Add WIP while-debugger-active SIGINT ignore handler

sigint2
Tyler Goodlet 2022-01-22 19:32:26 -05:00
parent 6e5590dad6
commit 28513bc601
1 changed files with 113 additions and 43 deletions

View File

@ -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,12 +481,85 @@ 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: if actor is not None:
log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n") log.pdb(f"\nAttaching pdb to actor: {actor.uid}\n")
@ -526,8 +591,13 @@ breakpoint = partial(
def _post_mortem(actor): def _post_mortem(actor):
__tracebackhide__ = True
# pdb = _mk_pdb()
with (
_open_pdb() as pdb,
disable_sigint(),
):
log.pdb(f"\nAttaching to pdb in crashed actor: {actor.uid}\n") log.pdb(f"\nAttaching to pdb in crashed actor: {actor.uid}\n")
pdb = _mk_pdb()
# custom Pdb post-mortem entry # custom Pdb post-mortem entry
pdbpp.xpm(Pdb=lambda: pdb) pdbpp.xpm(Pdb=lambda: pdb)