Kick off `.devx` subpkg for our dev tools B)

Where `.devx` is "developer experience", a hopefully broad enough subpkg
name for all the slick stuff planned to augment working on the actor
runtime 💥

Move the `._debug` module into the new subpkg and adjust rest of core
code base to reflect import path change. Also add a new
`.devx._debug.open_crash_handler()` manager for wrapping any sync code
outside a `trio.run()` which is handy for eventual CLI addons for
popular frameworks like `click`/`typer`.
multihomed
Tyler Goodlet 2023-09-28 14:14:50 -04:00
parent 3d0e95513c
commit fa9a9cfb1d
9 changed files with 99 additions and 22 deletions

View File

@ -45,7 +45,7 @@ from ._exceptions import (
ModuleNotExposed,
ContextCancelled,
)
from ._debug import (
from .devx import (
breakpoint,
pause,
pause_from_sync,

View File

@ -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':

View File

@ -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

View File

@ -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.

View File

@ -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:

View File

@ -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,
)

View File

@ -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

View File

@ -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 <https://www.gnu.org/licenses/>.
"""
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',
]

View File

@ -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