forked from goodboy/tractor
				
			Add WIP while-debugger-active SIGINT ignore handler
							parent
							
								
									6e5590dad6
								
							
						
					
					
						commit
						28513bc601
					
				|  | @ -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