forked from goodboy/tractor
1
0
Fork 0

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, ModuleNotExposed,
ContextCancelled, ContextCancelled,
) )
from ._debug import ( from .devx import (
breakpoint, breakpoint,
pause, pause,
pause_from_sync, pause_from_sync,

View File

@ -222,7 +222,7 @@ class Context:
) )
if self._cancel_called: if self._cancel_called:
# from ._debug import breakpoint # from .devx._debug import breakpoint
# await breakpoint() # await breakpoint()
# this is an expected cancel request response message # this is an expected cancel request response message
@ -247,7 +247,7 @@ class Context:
self._scope.cancel() self._scope.cancel()
# NOTE: this usage actually works here B) # NOTE: this usage actually works here B)
# from ._debug import breakpoint # from .devx._debug import breakpoint
# await breakpoint() # await breakpoint()
# XXX: this will break early callee results sending # 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}') log.cancel(f'Cancelling {side} side of context to {self.chan.uid}')
self._cancel_called = True self._cancel_called = True
# await _debug.breakpoint() # await devx._debug.breakpoint()
# breakpoint() # breakpoint()
if side == 'caller': if side == 'caller':

View File

@ -482,7 +482,7 @@ class Portal:
# were initiated by *this* side's task. # were initiated by *this* side's task.
if not ctx._cancel_called: if not ctx._cancel_called:
# XXX: this should NEVER happen! # XXX: this should NEVER happen!
# from ._debug import breakpoint # from .devx._debug import breakpoint
# await breakpoint() # await breakpoint()
raise raise
@ -564,7 +564,7 @@ class Portal:
# a "stop" msg for a stream), this can result in a deadlock # a "stop" msg for a stream), this can result in a deadlock
# where the root is waiting on the lock to clear but the # where the root is waiting on the lock to clear but the
# child has already cleared it and clobbered IPC. # 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() await maybe_wait_for_debugger()
# remove the context from runtime tracking # remove the context from runtime tracking

View File

@ -37,7 +37,7 @@ from ._runtime import (
Arbiter, Arbiter,
async_main, async_main,
) )
from . import _debug from .devx import _debug
from . import _spawn from . import _spawn
from . import _state from . import _state
from . import log from . import log
@ -89,7 +89,7 @@ async def open_root_actor(
# https://github.com/python-trio/trio/issues/1155#issuecomment-742964018 # https://github.com/python-trio/trio/issues/1155#issuecomment-742964018
builtin_bp_handler = sys.breakpointhook builtin_bp_handler = sys.breakpointhook
orig_bp_path: str | None = os.environ.get('PYTHONBREAKPOINT', None) 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 # attempt to retreive ``trio``'s sigint handler and stash it
# on our debugger lock state. # on our debugger lock state.
@ -137,7 +137,7 @@ async def open_root_actor(
# expose internal debug module to every actor allowing # expose internal debug module to every actor allowing
# for use of ``await tractor.breakpoint()`` # 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 # if debug mode get's enabled *at least* use that level of
# logging for some informative console prompts. # logging for some informative console prompts.

View File

@ -58,7 +58,7 @@ from ._exceptions import (
ContextCancelled, ContextCancelled,
TransportClosed, TransportClosed,
) )
from . import _debug from .devx import _debug
from ._discovery import get_registry from ._discovery import get_registry
from ._portal import Portal from ._portal import Portal
from . import _state from . import _state
@ -264,7 +264,6 @@ async def _invoke(
cs: trio.CancelScope = ctx._scope cs: trio.CancelScope = ctx._scope
if cs.cancel_called: if cs.cancel_called:
canceller = ctx._cancelled_remote canceller = ctx._cancelled_remote
# await _debug.breakpoint()
# NOTE / TODO: if we end up having # NOTE / TODO: if we end up having
# ``Actor._cancel_task()`` call # ``Actor._cancel_task()`` call
@ -536,7 +535,7 @@ class Actor:
self._parent_main_data = _mp_fixup_main._mp_figure_out_main() self._parent_main_data = _mp_fixup_main._mp_figure_out_main()
# always include debugging tools module # always include debugging tools module
enable_modules.append('tractor._debug') enable_modules.append('tractor.devx._debug')
self.enable_modules: dict[str, str] = {} self.enable_modules: dict[str, str] = {}
for name in enable_modules: for name in enable_modules:

View File

@ -35,7 +35,7 @@ from exceptiongroup import BaseExceptionGroup
import trio import trio
from trio_typing import TaskStatus from trio_typing import TaskStatus
from ._debug import ( from .devx._debug import (
maybe_wait_for_debugger, maybe_wait_for_debugger,
acquire_debug_lock, acquire_debug_lock,
) )

View File

@ -28,7 +28,7 @@ import warnings
from exceptiongroup import BaseExceptionGroup from exceptiongroup import BaseExceptionGroup
import trio 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 ._state import current_actor, is_main_process
from .log import get_logger, get_loglevel from .log import get_logger, get_loglevel
from ._runtime import Actor 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, cached_property,
) )
from contextlib import asynccontextmanager as acm from contextlib import asynccontextmanager as acm
from contextlib import contextmanager as cm
from typing import ( from typing import (
Any, Any,
Callable, Callable,
@ -44,22 +45,25 @@ from trio_typing import (
# Task, # Task,
) )
from .log import get_logger from ..log import get_logger
from ._discovery import get_root from .._discovery import get_root
from ._state import ( from .._state import (
is_root_process, is_root_process,
debug_mode, debug_mode,
) )
from ._exceptions import ( from .._exceptions import (
is_multi_cancelled, is_multi_cancelled,
ContextCancelled, ContextCancelled,
) )
from ._ipc import Channel from .._ipc import Channel
log = get_logger(__name__) log = get_logger(__name__)
__all__ = ['breakpoint', 'post_mortem'] __all__ = [
'breakpoint',
'post_mortem',
]
class Lock: class Lock:
@ -390,7 +394,7 @@ async def wait_for_parent_stdin_hijack(
# this syncs to child's ``Context.started()`` call. # this syncs to child's ``Context.started()`` call.
async with portal.open_context( async with portal.open_context(
tractor._debug.lock_tty_for_child, lock_tty_for_child,
subactor_uid=actor_uid, subactor_uid=actor_uid,
) as (ctx, val): ) as (ctx, val):
@ -855,7 +859,7 @@ pause = partial(
_pause, _pause,
_set_trace, _set_trace,
) )
pp = pause # short-hand for "pause point" # pp = pause # short-hand for "pause point"
async def breakpoint(**kwargs): async def breakpoint(**kwargs):
@ -1008,3 +1012,32 @@ async def maybe_wait_for_debugger(
log.debug( log.debug(
'Root acquired TTY LOCK' '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