diff --git a/tractor/__init__.py b/tractor/__init__.py index c653ec0..149d4d2 100644 --- a/tractor/__init__.py +++ b/tractor/__init__.py @@ -45,7 +45,7 @@ from ._exceptions import ( ModuleNotExposed, ContextCancelled, ) -from ._debug import ( +from .devx import ( breakpoint, pause, pause_from_sync, diff --git a/tractor/_context.py b/tractor/_context.py index 054f783..e35188c 100644 --- a/tractor/_context.py +++ b/tractor/_context.py @@ -222,7 +222,7 @@ class Context: ) if self._cancel_called: - # from ._debug import breakpoint + # from .devx._debug import breakpoint # await breakpoint() # this is an expected cancel request response message @@ -247,7 +247,7 @@ class Context: self._scope.cancel() # NOTE: this usage actually works here B) - # from ._debug import breakpoint + # from .devx._debug import breakpoint # await breakpoint() # XXX: this will break early callee results sending @@ -277,7 +277,7 @@ class Context: log.cancel(f'Cancelling {side} side of context to {self.chan.uid}') self._cancel_called = True - # await _debug.breakpoint() + # await devx._debug.breakpoint() # breakpoint() if side == 'caller': diff --git a/tractor/_portal.py b/tractor/_portal.py index 53684b4..9016eda 100644 --- a/tractor/_portal.py +++ b/tractor/_portal.py @@ -482,7 +482,7 @@ class Portal: # were initiated by *this* side's task. if not ctx._cancel_called: # XXX: this should NEVER happen! - # from ._debug import breakpoint + # from .devx._debug import breakpoint # await breakpoint() raise @@ -564,7 +564,7 @@ class Portal: # a "stop" msg for a stream), this can result in a deadlock # where the root is waiting on the lock to clear but the # child has already cleared it and clobbered IPC. - from ._debug import maybe_wait_for_debugger + from .devx._debug import maybe_wait_for_debugger await maybe_wait_for_debugger() # remove the context from runtime tracking diff --git a/tractor/_root.py b/tractor/_root.py index f64aa69..b117c2c 100644 --- a/tractor/_root.py +++ b/tractor/_root.py @@ -37,7 +37,7 @@ from ._runtime import ( Arbiter, async_main, ) -from . import _debug +from .devx import _debug from . import _spawn from . import _state from . import log @@ -89,7 +89,7 @@ async def open_root_actor( # https://github.com/python-trio/trio/issues/1155#issuecomment-742964018 builtin_bp_handler = sys.breakpointhook orig_bp_path: str | None = os.environ.get('PYTHONBREAKPOINT', None) - os.environ['PYTHONBREAKPOINT'] = 'tractor._debug.pause_from_sync' + os.environ['PYTHONBREAKPOINT'] = 'tractor.devx._debug.pause_from_sync' # attempt to retreive ``trio``'s sigint handler and stash it # on our debugger lock state. @@ -137,7 +137,7 @@ async def open_root_actor( # expose internal debug module to every actor allowing # for use of ``await tractor.breakpoint()`` - enable_modules.append('tractor._debug') + enable_modules.append('tractor.devx._debug') # if debug mode get's enabled *at least* use that level of # logging for some informative console prompts. diff --git a/tractor/_runtime.py b/tractor/_runtime.py index 16f105c..0f8cc7a 100644 --- a/tractor/_runtime.py +++ b/tractor/_runtime.py @@ -58,7 +58,7 @@ from ._exceptions import ( ContextCancelled, TransportClosed, ) -from . import _debug +from .devx import _debug from ._discovery import get_registry from ._portal import Portal from . import _state @@ -264,7 +264,6 @@ async def _invoke( cs: trio.CancelScope = ctx._scope if cs.cancel_called: canceller = ctx._cancelled_remote - # await _debug.breakpoint() # NOTE / TODO: if we end up having # ``Actor._cancel_task()`` call @@ -536,7 +535,7 @@ class Actor: self._parent_main_data = _mp_fixup_main._mp_figure_out_main() # always include debugging tools module - enable_modules.append('tractor._debug') + enable_modules.append('tractor.devx._debug') self.enable_modules: dict[str, str] = {} for name in enable_modules: diff --git a/tractor/_spawn.py b/tractor/_spawn.py index 985b810..9c61855 100644 --- a/tractor/_spawn.py +++ b/tractor/_spawn.py @@ -35,7 +35,7 @@ from exceptiongroup import BaseExceptionGroup import trio from trio_typing import TaskStatus -from ._debug import ( +from .devx._debug import ( maybe_wait_for_debugger, acquire_debug_lock, ) diff --git a/tractor/_supervise.py b/tractor/_supervise.py index e8599fd..7851d9f 100644 --- a/tractor/_supervise.py +++ b/tractor/_supervise.py @@ -28,7 +28,7 @@ import warnings from exceptiongroup import BaseExceptionGroup import trio -from ._debug import maybe_wait_for_debugger +from .devx._debug import maybe_wait_for_debugger from ._state import current_actor, is_main_process from .log import get_logger, get_loglevel from ._runtime import Actor diff --git a/tractor/devx/__init__.py b/tractor/devx/__init__.py new file mode 100644 index 0000000..e24405a --- /dev/null +++ b/tractor/devx/__init__.py @@ -0,0 +1,45 @@ +# tractor: structured concurrent "actors". +# Copyright 2018-eternity Tyler Goodlet. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +""" +Runtime "developer experience" utils and addons to aid our +(advanced) users and core devs in building distributed applications +and working with/on the actor runtime. + +""" +from ._debug import ( + maybe_wait_for_debugger, + acquire_debug_lock, + breakpoint, + pause, + pause_from_sync, + shield_sigint_handler, + MultiActorPdb, + open_crash_handler, + post_mortem, +) + +__all__ = [ + 'maybe_wait_for_debugger', + 'acquire_debug_lock', + 'breakpoint', + 'pause', + 'pause_from_sync', + 'shield_sigint_handler', + 'MultiActorPdb', + 'open_crash_handler', + 'post_mortem', +] diff --git a/tractor/_debug.py b/tractor/devx/_debug.py similarity index 96% rename from tractor/_debug.py rename to tractor/devx/_debug.py index d5f5f4f..6575c22 100644 --- a/tractor/_debug.py +++ b/tractor/devx/_debug.py @@ -28,6 +28,7 @@ from functools import ( cached_property, ) from contextlib import asynccontextmanager as acm +from contextlib import contextmanager as cm from typing import ( Any, Callable, @@ -44,22 +45,25 @@ from trio_typing import ( # Task, ) -from .log import get_logger -from ._discovery import get_root -from ._state import ( +from ..log import get_logger +from .._discovery import get_root +from .._state import ( is_root_process, debug_mode, ) -from ._exceptions import ( +from .._exceptions import ( is_multi_cancelled, ContextCancelled, ) -from ._ipc import Channel +from .._ipc import Channel log = get_logger(__name__) -__all__ = ['breakpoint', 'post_mortem'] +__all__ = [ + 'breakpoint', + 'post_mortem', +] class Lock: @@ -390,7 +394,7 @@ async def wait_for_parent_stdin_hijack( # this syncs to child's ``Context.started()`` call. async with portal.open_context( - tractor._debug.lock_tty_for_child, + lock_tty_for_child, subactor_uid=actor_uid, ) as (ctx, val): @@ -855,7 +859,7 @@ pause = partial( _pause, _set_trace, ) -pp = pause # short-hand for "pause point" +# pp = pause # short-hand for "pause point" async def breakpoint(**kwargs): @@ -1008,3 +1012,32 @@ async def maybe_wait_for_debugger( log.debug( 'Root acquired TTY LOCK' ) + + +# TODO: better naming and what additionals? +# - optional runtime plugging? +# - detection for sync vs. async code? +# - specialized REPL entry when in distributed mode? +@cm +def open_crash_handler( + catch: set[BaseException] = { + Exception, + BaseException, + } +): + ''' + Generic "post mortem" crash handler using `pdbp` REPL debugger. + + We expose this as a CLI framework addon to both `click` and + `typer` users so they can quickly wrap cmd endpoints which get + automatically wrapped to use the runtime's `debug_mode: bool` + AND `pdbp.pm()` around any code that is PRE-runtime entry + - any sync code which runs BEFORE the main call to + `trio.run()`. + + ''' + try: + yield + except tuple(catch): + pdbp.xpm() + raise