Skip to content

Commit

Permalink
Add WIP while-debugger-active SIGINT ignore handler
Browse files Browse the repository at this point in the history
  • Loading branch information
goodboy committed Jan 23, 2022
1 parent 0b51ebf commit c4346d9
Showing 1 changed file with 111 additions and 41 deletions.
152 changes: 111 additions & 41 deletions tractor/_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import sys
from functools import partial
from contextlib import asynccontextmanager as acm
from contextlib import contextmanager as cm
from typing import (
Tuple,
Optional,
Expand All @@ -35,7 +36,6 @@
from trio_typing import TaskStatus

from .log import get_logger
from . import _state
from ._discovery import get_root
from ._state import is_root_process, debug_mode
from ._exceptions import is_multi_cancelled
Expand Down Expand Up @@ -81,6 +81,7 @@ class TractorConfig(pdbpp.DefaultConfig):
"""Custom ``pdbpp`` goodness.
"""
# sticky_by_default = True
enable_hidden_frames = False


class PdbwTeardown(pdbpp.Pdb):
Expand Down Expand Up @@ -219,22 +220,6 @@ async def _acquire_debug_lock(
log.debug(f"TTY lock released, remote task: {task_name}:{uid}")


def handler(signum, frame, *args):
"""Specialized debugger compatible SIGINT handler.
In childred we always ignore to avoid deadlocks since cancellation
should always be managed by the parent supervising actor. The root
is always cancelled on ctrl-c.
"""
if is_root_process():
tractor.current_actor().cancel_soon()
else:
print(
"tractor ignores SIGINT while in debug mode\n"
"If you have a special need for it please open an issue.\n"
)


@tractor.context
async def _hijack_stdin_for_child(

Expand All @@ -260,7 +245,10 @@ async def _hijack_stdin_for_child(

log.debug(f"Actor {subactor_uid} is WAITING on stdin hijack lock")

with trio.CancelScope(shield=True):
with (
trio.CancelScope(shield=True),
disable_sigint(),
):

try:
lock = None
Expand Down Expand Up @@ -374,6 +362,8 @@ async def _breakpoint(
in the root or a subactor.
'''
__tracebackhide__ = True

# TODO: is it possible to debug a trio.Cancelled except block?
# right now it seems like we can kinda do with by shielding
# around ``tractor.breakpoint()`` but not if we move the shielded
Expand Down Expand Up @@ -474,10 +464,12 @@ def teardown():
# block here one (at the appropriate frame *up*) where
# ``breakpoint()`` was awaited and begin handling stdio.
log.debug("Entering the synchronous world of pdb")

debug_func(actor)


def _mk_pdb() -> PdbwTeardown:
@cm
def _open_pdb() -> PdbwTeardown:

# XXX: setting these flags on the pdb instance are absolutely
# critical to having ctrl-c work in the ``trio`` standard way! The
Expand All @@ -489,34 +481,107 @@ def _mk_pdb() -> PdbwTeardown:
pdb.allow_kbdint = True
pdb.nosigint = True

return pdb
try:
yield pdb
except:
# finally:
_pdb_release_hook()


def _set_trace(actor=None):
pdb = _mk_pdb()
def disable_sigint_in_pdb(signum, frame, *args):
'''
Specialized debugger compatible SIGINT handler.
if actor is not None:
log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n")
In childred we always ignore to avoid deadlocks since cancellation
should always be managed by the parent supervising actor. The root
is always cancelled on ctrl-c.
pdb.set_trace(
# start 2 levels up in user code
frame=sys._getframe().f_back.f_back,
'''
actor = tractor.current_actor()
if not actor._cancel_called:
log.pdb(
f"{actor.uid} is in debug and has not been cancelled, "
"ignoring SIGINT\n"
)

else:
# we entered the global ``breakpoint()`` built-in from sync code
global _local_task_in_debug, _pdb_release_hook
_local_task_in_debug = 'sync'
log.pdb(
f"{actor.uid} is already cancelling.."
)

def nuttin():
global _global_actor_in_debug
in_debug = _global_actor_in_debug
if (
is_root_process()
and in_debug
):
log.pdb(f'Root SIGINT disabled while {_global_actor_in_debug} is debugging')

if in_debug[0] != 'root':
pass
else:
# actor.cancel_soon()
raise KeyboardInterrupt


@cm
def disable_sigint():
__tracebackhide__ = True

# ensure the ``contextlib.contextmanager`` frame inside the wrapping
# ``.__exit__()`` method isn't shown either.
import sys
frame = sys._getframe()
frame.f_back.f_globals['__tracebackhide__'] = True
# NOTE: this seems like a form of cpython bug wherein
# it's likely that ``functools.WRAPPER_ASSIGNMENTS`` should
# probably contain this attr name?

# for manual debugging if necessary
# pdb.set_trace()

import signal
orig_handler = signal.signal(
signal.SIGINT,
disable_sigint_in_pdb
)
try:
yield
finally:
signal.signal(
signal.SIGINT,
orig_handler
)

_pdb_release_hook = nuttin

pdb.set_trace(
# start 2 levels up in user code
frame=sys._getframe().f_back,
)
def _set_trace(actor=None):
__tracebackhide__ = True
# pdb = _open_pdb()
with (
_open_pdb() as pdb,
disable_sigint(),
):
if actor is not None:
log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n")

pdb.set_trace(
# start 2 levels up in user code
frame=sys._getframe().f_back.f_back,
)

else:
# we entered the global ``breakpoint()`` built-in from sync code
global _local_task_in_debug, _pdb_release_hook
_local_task_in_debug = 'sync'

def nuttin():
pass

_pdb_release_hook = nuttin

pdb.set_trace(
# start 2 levels up in user code
frame=sys._getframe().f_back,
)


breakpoint = partial(
Expand All @@ -526,11 +591,16 @@ def nuttin():


def _post_mortem(actor):
log.pdb(f"\nAttaching to pdb in crashed actor: {actor.uid}\n")
pdb = _mk_pdb()
__tracebackhide__ = True
# pdb = _mk_pdb()
with (
_open_pdb() as pdb,
disable_sigint(),
):
log.pdb(f"\nAttaching to pdb in crashed actor: {actor.uid}\n")

# custom Pdb post-mortem entry
pdbpp.xpm(Pdb=lambda: pdb)
# custom Pdb post-mortem entry
pdbpp.xpm(Pdb=lambda: pdb)


post_mortem = partial(
Expand Down

0 comments on commit c4346d9

Please sign in to comment.