forked from goodboy/tractor
Add WIP while-debugger-active SIGINT ignore handler
parent
4902e184e9
commit
0503142332
|
@ -22,6 +22,7 @@ import bdb
|
||||||
import sys
|
import sys
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from contextlib import asynccontextmanager as acm
|
from contextlib import asynccontextmanager as acm
|
||||||
|
from contextlib import contextmanager as cm
|
||||||
from typing import (
|
from typing import (
|
||||||
Tuple,
|
Tuple,
|
||||||
Optional,
|
Optional,
|
||||||
|
@ -35,7 +36,6 @@ import trio
|
||||||
from trio_typing import TaskStatus
|
from trio_typing import TaskStatus
|
||||||
|
|
||||||
from .log import get_logger
|
from .log import get_logger
|
||||||
from . import _state
|
|
||||||
from ._discovery import get_root
|
from ._discovery import get_root
|
||||||
from ._state import is_root_process, debug_mode
|
from ._state import is_root_process, debug_mode
|
||||||
from ._exceptions import is_multi_cancelled
|
from ._exceptions import is_multi_cancelled
|
||||||
|
@ -81,6 +81,7 @@ class TractorConfig(pdbpp.DefaultConfig):
|
||||||
"""Custom ``pdbpp`` goodness.
|
"""Custom ``pdbpp`` goodness.
|
||||||
"""
|
"""
|
||||||
# sticky_by_default = True
|
# sticky_by_default = True
|
||||||
|
enable_hidden_frames = False
|
||||||
|
|
||||||
|
|
||||||
class PdbwTeardown(pdbpp.Pdb):
|
class PdbwTeardown(pdbpp.Pdb):
|
||||||
|
@ -219,22 +220,6 @@ async def _acquire_debug_lock(
|
||||||
log.debug(f"TTY lock released, remote task: {task_name}:{uid}")
|
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
|
@tractor.context
|
||||||
async def _hijack_stdin_for_child(
|
async def _hijack_stdin_for_child(
|
||||||
|
|
||||||
|
@ -260,7 +245,10 @@ async def _hijack_stdin_for_child(
|
||||||
|
|
||||||
log.debug(f"Actor {subactor_uid} is WAITING on stdin hijack lock")
|
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:
|
try:
|
||||||
lock = None
|
lock = None
|
||||||
|
@ -374,6 +362,8 @@ async def _breakpoint(
|
||||||
in the root or a subactor.
|
in the root or a subactor.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
__tracebackhide__ = True
|
||||||
|
|
||||||
# TODO: is it possible to debug a trio.Cancelled except block?
|
# TODO: is it possible to debug a trio.Cancelled except block?
|
||||||
# right now it seems like we can kinda do with by shielding
|
# right now it seems like we can kinda do with by shielding
|
||||||
# around ``tractor.breakpoint()`` but not if we move the shielded
|
# around ``tractor.breakpoint()`` but not if we move the shielded
|
||||||
|
@ -474,10 +464,12 @@ async def _breakpoint(
|
||||||
# block here one (at the appropriate frame *up*) where
|
# block here one (at the appropriate frame *up*) where
|
||||||
# ``breakpoint()`` was awaited and begin handling stdio.
|
# ``breakpoint()`` was awaited and begin handling stdio.
|
||||||
log.debug("Entering the synchronous world of pdb")
|
log.debug("Entering the synchronous world of pdb")
|
||||||
|
|
||||||
debug_func(actor)
|
debug_func(actor)
|
||||||
|
|
||||||
|
|
||||||
def _mk_pdb() -> PdbwTeardown:
|
@cm
|
||||||
|
def _open_pdb() -> PdbwTeardown:
|
||||||
|
|
||||||
# XXX: setting these flags on the pdb instance are absolutely
|
# XXX: setting these flags on the pdb instance are absolutely
|
||||||
# critical to having ctrl-c work in the ``trio`` standard way! The
|
# critical to having ctrl-c work in the ``trio`` standard way! The
|
||||||
|
@ -489,34 +481,107 @@ def _mk_pdb() -> PdbwTeardown:
|
||||||
pdb.allow_kbdint = True
|
pdb.allow_kbdint = True
|
||||||
pdb.nosigint = True
|
pdb.nosigint = True
|
||||||
|
|
||||||
return pdb
|
try:
|
||||||
|
yield pdb
|
||||||
|
except:
|
||||||
|
# finally:
|
||||||
|
_pdb_release_hook()
|
||||||
|
|
||||||
|
|
||||||
|
def disable_sigint_in_pdb(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.
|
||||||
|
|
||||||
|
'''
|
||||||
|
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:
|
||||||
|
log.pdb(
|
||||||
|
f"{actor.uid} is already cancelling.."
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _set_trace(actor=None):
|
def _set_trace(actor=None):
|
||||||
pdb = _mk_pdb()
|
__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")
|
||||||
|
|
||||||
if actor is not None:
|
pdb.set_trace(
|
||||||
log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n")
|
# start 2 levels up in user code
|
||||||
|
frame=sys._getframe().f_back.f_back,
|
||||||
|
)
|
||||||
|
|
||||||
pdb.set_trace(
|
else:
|
||||||
# start 2 levels up in user code
|
# we entered the global ``breakpoint()`` built-in from sync code
|
||||||
frame=sys._getframe().f_back.f_back,
|
global _local_task_in_debug, _pdb_release_hook
|
||||||
)
|
_local_task_in_debug = 'sync'
|
||||||
|
|
||||||
else:
|
def nuttin():
|
||||||
# we entered the global ``breakpoint()`` built-in from sync code
|
pass
|
||||||
global _local_task_in_debug, _pdb_release_hook
|
|
||||||
_local_task_in_debug = 'sync'
|
|
||||||
|
|
||||||
def nuttin():
|
_pdb_release_hook = nuttin
|
||||||
pass
|
|
||||||
|
|
||||||
_pdb_release_hook = nuttin
|
pdb.set_trace(
|
||||||
|
# start 2 levels up in user code
|
||||||
pdb.set_trace(
|
frame=sys._getframe().f_back,
|
||||||
# start 2 levels up in user code
|
)
|
||||||
frame=sys._getframe().f_back,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
breakpoint = partial(
|
breakpoint = partial(
|
||||||
|
@ -526,11 +591,16 @@ breakpoint = partial(
|
||||||
|
|
||||||
|
|
||||||
def _post_mortem(actor):
|
def _post_mortem(actor):
|
||||||
log.pdb(f"\nAttaching to pdb in crashed actor: {actor.uid}\n")
|
__tracebackhide__ = True
|
||||||
pdb = _mk_pdb()
|
# 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
|
# custom Pdb post-mortem entry
|
||||||
pdbpp.xpm(Pdb=lambda: pdb)
|
pdbpp.xpm(Pdb=lambda: pdb)
|
||||||
|
|
||||||
|
|
||||||
post_mortem = partial(
|
post_mortem = partial(
|
||||||
|
|
Loading…
Reference in New Issue