Re-impl as `DebugStatus.maybe_enter_repl_fixture()`
Dropping the `_maybe_open_repl_fixture()` approach and instead using a `DebugStatus._fixture_stack = ExitStack()` which provides for much simpler support around both sync and async pausing APIs thanks to only invoking `repl_fixture.__exit__()` on actual `PdbREPL` interaction being complete! Deats, - all `repl_fixture` detection logic still happens in one place (the new method) but we aren't limited to closing it via an immediate post REPL `.__exit__()` call which instead is triggered by, - `DebugStatus.release()` which now calls `._fixture_stack.close()` and thus only invokes `repl_fixture.__exit__()` when user REPL-ing is **actually complete** an arbitrary amount of debugging time later. - include the notes for `@acm` support above the new method, though not sure if they're as relevant any more? Benefits, - we can drop the previously added indent levels from `_enter_repl_sync()` and `_post_mortem()`. - now we automatically have support for the `.pause_from_sync()` API since `_enter_repl_sync()` doesn't close the prior `_maybe_open_repl_fixture()` immediately when `debug_func=None`; the user's `__exit__()` is only ever called once `.release()` is. Other, - add big 'CASE' comments around the various blocks in `.pause_from_sync()`, i was having trouble figuring out which i was using from a `breakpoint()` in a dependent app..repl_fixture
parent
d716c57234
commit
2a37f6abdd
|
@ -64,7 +64,6 @@ from tractor._exceptions import (
|
||||||
)
|
)
|
||||||
from ._trace import (
|
from ._trace import (
|
||||||
_pause,
|
_pause,
|
||||||
_maybe_open_repl_fixture,
|
|
||||||
)
|
)
|
||||||
from ._tty_lock import (
|
from ._tty_lock import (
|
||||||
DebugStatus,
|
DebugStatus,
|
||||||
|
@ -143,65 +142,65 @@ def _post_mortem(
|
||||||
'''
|
'''
|
||||||
__tracebackhide__: bool = hide_tb
|
__tracebackhide__: bool = hide_tb
|
||||||
|
|
||||||
with _maybe_open_repl_fixture(
|
# maybe enter any user fixture
|
||||||
|
enter_repl: bool = DebugStatus.maybe_enter_repl_fixture(
|
||||||
repl=repl,
|
repl=repl,
|
||||||
repl_fixture=repl_fixture,
|
repl_fixture=repl_fixture,
|
||||||
boxed_maybe_exc=boxed_maybe_exc,
|
boxed_maybe_exc=boxed_maybe_exc,
|
||||||
) as enter_repl:
|
)
|
||||||
if not enter_repl:
|
if not enter_repl:
|
||||||
return
|
return
|
||||||
|
try:
|
||||||
|
actor: Actor = current_actor()
|
||||||
|
actor_repr: str = str(actor.uid)
|
||||||
|
# ^TODO, instead a nice runtime-info + maddr + uid?
|
||||||
|
# -[ ] impl a `Actor.__repr()__`??
|
||||||
|
# |_ <task>:<thread> @ <actor>
|
||||||
|
|
||||||
try:
|
except NoRuntime:
|
||||||
actor: Actor = current_actor()
|
actor_repr: str = '<no-actor-runtime?>'
|
||||||
actor_repr: str = str(actor.uid)
|
|
||||||
# ^TODO, instead a nice runtime-info + maddr + uid?
|
|
||||||
# -[ ] impl a `Actor.__repr()__`??
|
|
||||||
# |_ <task>:<thread> @ <actor>
|
|
||||||
|
|
||||||
except NoRuntime:
|
try:
|
||||||
actor_repr: str = '<no-actor-runtime?>'
|
task_repr: Task = trio.lowlevel.current_task()
|
||||||
|
except RuntimeError:
|
||||||
|
task_repr: str = '<unknown-Task>'
|
||||||
|
|
||||||
try:
|
# TODO: print the actor supervion tree up to the root
|
||||||
task_repr: Task = trio.lowlevel.current_task()
|
# here! Bo
|
||||||
except RuntimeError:
|
log.pdb(
|
||||||
task_repr: str = '<unknown-Task>'
|
f'{_crash_msg}\n'
|
||||||
|
f'x>(\n'
|
||||||
|
f' |_ {task_repr} @ {actor_repr}\n'
|
||||||
|
|
||||||
# TODO: print the actor supervion tree up to the root
|
)
|
||||||
# here! Bo
|
|
||||||
log.pdb(
|
|
||||||
f'{_crash_msg}\n'
|
|
||||||
f'x>(\n'
|
|
||||||
f' |_ {task_repr} @ {actor_repr}\n'
|
|
||||||
|
|
||||||
)
|
# XXX NOTE(s) on `pdbp.xpm()` version..
|
||||||
|
#
|
||||||
|
# - seems to lose the up-stack tb-info?
|
||||||
|
# - currently we're (only) replacing this from `pdbp.xpm()`
|
||||||
|
# to add the `end=''` to the print XD
|
||||||
|
#
|
||||||
|
print(traceback.format_exc(), end='')
|
||||||
|
caller_frame: FrameType = api_frame.f_back
|
||||||
|
|
||||||
# XXX NOTE(s) on `pdbp.xpm()` version..
|
# NOTE, see the impl details of these in the lib to
|
||||||
#
|
# understand usage:
|
||||||
# - seems to lose the up-stack tb-info?
|
# - `pdbp.post_mortem()`
|
||||||
# - currently we're (only) replacing this from `pdbp.xpm()`
|
# - `pdbp.xps()`
|
||||||
# to add the `end=''` to the print XD
|
# - `bdb.interaction()`
|
||||||
#
|
repl.reset()
|
||||||
print(traceback.format_exc(), end='')
|
repl.interaction(
|
||||||
caller_frame: FrameType = api_frame.f_back
|
frame=caller_frame,
|
||||||
|
# frame=None,
|
||||||
|
traceback=tb,
|
||||||
|
)
|
||||||
|
|
||||||
# NOTE, see the impl details of these in the lib to
|
# XXX NOTE XXX: this is abs required to avoid hangs!
|
||||||
# understand usage:
|
#
|
||||||
# - `pdbp.post_mortem()`
|
# Since we presume the post-mortem was enaged to
|
||||||
# - `pdbp.xps()`
|
# a task-ending error, we MUST release the local REPL request
|
||||||
# - `bdb.interaction()`
|
# so that not other local task nor the root remains blocked!
|
||||||
repl.reset()
|
DebugStatus.release()
|
||||||
repl.interaction(
|
|
||||||
frame=caller_frame,
|
|
||||||
# frame=None,
|
|
||||||
traceback=tb,
|
|
||||||
)
|
|
||||||
|
|
||||||
# XXX NOTE XXX: this is abs required to avoid hangs!
|
|
||||||
#
|
|
||||||
# Since we presume the post-mortem was enaged to
|
|
||||||
# a task-ending error, we MUST release the local REPL request
|
|
||||||
# so that not other local task nor the root remains blocked!
|
|
||||||
DebugStatus.release()
|
|
||||||
|
|
||||||
|
|
||||||
async def post_mortem(
|
async def post_mortem(
|
||||||
|
|
|
@ -28,8 +28,6 @@ import asyncio
|
||||||
import bdb
|
import bdb
|
||||||
from contextlib import (
|
from contextlib import (
|
||||||
AbstractContextManager,
|
AbstractContextManager,
|
||||||
contextmanager as cm,
|
|
||||||
nullcontext,
|
|
||||||
)
|
)
|
||||||
from functools import (
|
from functools import (
|
||||||
partial,
|
partial,
|
||||||
|
@ -37,7 +35,6 @@ from functools import (
|
||||||
import inspect
|
import inspect
|
||||||
import threading
|
import threading
|
||||||
from typing import (
|
from typing import (
|
||||||
Iterator,
|
|
||||||
Callable,
|
Callable,
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
)
|
)
|
||||||
|
@ -89,7 +86,7 @@ if TYPE_CHECKING:
|
||||||
from tractor._runtime import (
|
from tractor._runtime import (
|
||||||
Actor,
|
Actor,
|
||||||
)
|
)
|
||||||
from ._post_mortem import BoxedMaybeException
|
# from ._post_mortem import BoxedMaybeException
|
||||||
from ._repl import PdbREPL
|
from ._repl import PdbREPL
|
||||||
|
|
||||||
log = get_logger(__package__)
|
log = get_logger(__package__)
|
||||||
|
@ -99,69 +96,6 @@ _repl_fail_msg: str|None = (
|
||||||
'Failed to REPl via `_pause()` '
|
'Failed to REPl via `_pause()` '
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO, support @acm?
|
|
||||||
# -[ ] what about a return-proto for determining
|
|
||||||
# whether the REPL should be allowed to enage?
|
|
||||||
# -[ ] consider factoring this `_repl_fixture` block into
|
|
||||||
# a common @cm somehow so it can be repurposed both here and
|
|
||||||
# in `._pause()`??
|
|
||||||
# -[ ] we could also use the `ContextDecorator`-type in that
|
|
||||||
# case to simply decorate the `_enter_repl_sync()` closure?
|
|
||||||
# |_https://docs.python.org/3/library/contextlib.html#using-a-context-manager-as-a-function-decorator
|
|
||||||
@cm
|
|
||||||
def _maybe_open_repl_fixture(
|
|
||||||
repl: PdbREPL,
|
|
||||||
# ^XXX **always provided** by the low-level REPL-invoker,
|
|
||||||
# - _post_mortem()
|
|
||||||
# - _pause()
|
|
||||||
|
|
||||||
repl_fixture: (
|
|
||||||
AbstractContextManager[bool]
|
|
||||||
|None
|
|
||||||
) = None,
|
|
||||||
boxed_maybe_exc: BoxedMaybeException|None = None,
|
|
||||||
) -> Iterator[bool]:
|
|
||||||
'''
|
|
||||||
Maybe open a pre/post REPL entry "fixture" `@cm` provided by the
|
|
||||||
user, the caller should use the delivered `bool` to determine
|
|
||||||
whether to engage the `PdbREPL`.
|
|
||||||
|
|
||||||
'''
|
|
||||||
if not (
|
|
||||||
repl_fixture
|
|
||||||
or
|
|
||||||
(rt_repl_fixture := _state._runtime_vars.get('repl_fixture'))
|
|
||||||
):
|
|
||||||
_repl_fixture = nullcontext(
|
|
||||||
enter_result=True,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
_repl_fixture = (
|
|
||||||
repl_fixture
|
|
||||||
or
|
|
||||||
rt_repl_fixture
|
|
||||||
)(
|
|
||||||
repl=repl,
|
|
||||||
maybe_bxerr=boxed_maybe_exc
|
|
||||||
)
|
|
||||||
|
|
||||||
with _repl_fixture as enter_repl:
|
|
||||||
|
|
||||||
# XXX when the fixture doesn't allow it, skip
|
|
||||||
# the crash-handler REPL and raise now!
|
|
||||||
if not enter_repl:
|
|
||||||
log.pdb(
|
|
||||||
f'pdbp-REPL blocked by a `repl_fixture()` which yielded `False` !\n'
|
|
||||||
f'repl_fixture: {repl_fixture}\n'
|
|
||||||
f'rt_repl_fixture: {rt_repl_fixture}\n'
|
|
||||||
)
|
|
||||||
yield False # no don't enter REPL
|
|
||||||
return
|
|
||||||
|
|
||||||
yield True # yes enter REPL
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def _pause(
|
async def _pause(
|
||||||
|
|
||||||
debug_func: Callable|partial|None,
|
debug_func: Callable|partial|None,
|
||||||
|
@ -255,90 +189,86 @@ async def _pause(
|
||||||
) -> None:
|
) -> None:
|
||||||
__tracebackhide__: bool = hide_tb
|
__tracebackhide__: bool = hide_tb
|
||||||
|
|
||||||
# TODO, support @acm?
|
# maybe enter any user fixture
|
||||||
# -[ ] what about a return-proto for determining
|
enter_repl: bool = DebugStatus.maybe_enter_repl_fixture(
|
||||||
# whether the REPL should be allowed to enage?
|
|
||||||
# nonlocal repl_fixture
|
|
||||||
|
|
||||||
with _maybe_open_repl_fixture(
|
|
||||||
repl=repl,
|
repl=repl,
|
||||||
repl_fixture=repl_fixture,
|
repl_fixture=repl_fixture,
|
||||||
) as enter_repl:
|
)
|
||||||
if not enter_repl:
|
if not enter_repl:
|
||||||
return
|
return
|
||||||
|
|
||||||
debug_func_name: str = (
|
debug_func_name: str = (
|
||||||
debug_func.func.__name__ if debug_func else 'None'
|
debug_func.func.__name__ if debug_func else 'None'
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: do we want to support using this **just** for the
|
# TODO: do we want to support using this **just** for the
|
||||||
# locking / common code (prolly to help address #320)?
|
# locking / common code (prolly to help address #320)?
|
||||||
task_status.started((task, repl))
|
task_status.started((task, repl))
|
||||||
try:
|
try:
|
||||||
if debug_func:
|
if debug_func:
|
||||||
# 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.devx(
|
log.devx(
|
||||||
'Entering sync world of the `pdb` REPL for task..\n'
|
'Entering sync world of the `pdb` REPL for task..\n'
|
||||||
f'{repl}\n'
|
f'{repl}\n'
|
||||||
f' |_{task}\n'
|
f' |_{task}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
# set local task on process-global state to avoid
|
# set local task on process-global state to avoid
|
||||||
# recurrent entries/requests from the same
|
# recurrent entries/requests from the same
|
||||||
# actor-local task.
|
# actor-local task.
|
||||||
DebugStatus.repl_task = task
|
DebugStatus.repl_task = task
|
||||||
if repl:
|
if repl:
|
||||||
DebugStatus.repl = repl
|
DebugStatus.repl = repl
|
||||||
else:
|
else:
|
||||||
log.error(
|
log.error(
|
||||||
'No REPl instance set before entering `debug_func`?\n'
|
'No REPl instance set before entering `debug_func`?\n'
|
||||||
f'{debug_func}\n'
|
f'{debug_func}\n'
|
||||||
)
|
|
||||||
|
|
||||||
# invoke the low-level REPL activation routine which itself
|
|
||||||
# should call into a `Pdb.set_trace()` of some sort.
|
|
||||||
debug_func(
|
|
||||||
repl=repl,
|
|
||||||
hide_tb=hide_tb,
|
|
||||||
**debug_func_kwargs,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: maybe invert this logic and instead
|
# invoke the low-level REPL activation routine which itself
|
||||||
# do `assert debug_func is None` when
|
# should call into a `Pdb.set_trace()` of some sort.
|
||||||
# `called_from_sync`?
|
debug_func(
|
||||||
else:
|
repl=repl,
|
||||||
if (
|
hide_tb=hide_tb,
|
||||||
called_from_sync
|
**debug_func_kwargs,
|
||||||
and
|
|
||||||
not DebugStatus.is_main_trio_thread()
|
|
||||||
):
|
|
||||||
assert called_from_bg_thread
|
|
||||||
assert DebugStatus.repl_task is not task
|
|
||||||
|
|
||||||
return (task, repl)
|
|
||||||
|
|
||||||
except trio.Cancelled:
|
|
||||||
log.exception(
|
|
||||||
'Cancelled during invoke of internal\n\n'
|
|
||||||
f'`debug_func = {debug_func_name}`\n'
|
|
||||||
)
|
)
|
||||||
# XXX NOTE: DON'T release lock yet
|
|
||||||
raise
|
|
||||||
|
|
||||||
except BaseException:
|
# TODO: maybe invert this logic and instead
|
||||||
__tracebackhide__: bool = False
|
# do `assert debug_func is None` when
|
||||||
log.exception(
|
# `called_from_sync`?
|
||||||
'Failed to invoke internal\n\n'
|
else:
|
||||||
f'`debug_func = {debug_func_name}`\n'
|
if (
|
||||||
)
|
called_from_sync
|
||||||
# NOTE: OW this is ONLY called from the
|
and
|
||||||
# `.set_continue/next` hooks!
|
not DebugStatus.is_main_trio_thread()
|
||||||
DebugStatus.release(cancel_req_task=True)
|
):
|
||||||
|
assert called_from_bg_thread
|
||||||
|
assert DebugStatus.repl_task is not task
|
||||||
|
|
||||||
raise
|
return (task, repl)
|
||||||
|
|
||||||
log.devx(
|
except trio.Cancelled:
|
||||||
|
log.exception(
|
||||||
|
'Cancelled during invoke of internal\n\n'
|
||||||
|
f'`debug_func = {debug_func_name}`\n'
|
||||||
|
)
|
||||||
|
# XXX NOTE: DON'T release lock yet
|
||||||
|
raise
|
||||||
|
|
||||||
|
except BaseException:
|
||||||
|
__tracebackhide__: bool = False
|
||||||
|
log.exception(
|
||||||
|
'Failed to invoke internal\n\n'
|
||||||
|
f'`debug_func = {debug_func_name}`\n'
|
||||||
|
)
|
||||||
|
# NOTE: OW this is ONLY called from the
|
||||||
|
# `.set_continue/next` hooks!
|
||||||
|
DebugStatus.release(cancel_req_task=True)
|
||||||
|
|
||||||
|
raise
|
||||||
|
|
||||||
|
log.debug(
|
||||||
'Entering `._pause()` for requesting task\n'
|
'Entering `._pause()` for requesting task\n'
|
||||||
f'|_{task}\n'
|
f'|_{task}\n'
|
||||||
)
|
)
|
||||||
|
@ -352,7 +282,7 @@ async def _pause(
|
||||||
or
|
or
|
||||||
DebugStatus.repl_release.is_set()
|
DebugStatus.repl_release.is_set()
|
||||||
):
|
):
|
||||||
log.devx(
|
log.debug(
|
||||||
'Setting new `DebugStatus.repl_release: trio.Event` for requesting task\n'
|
'Setting new `DebugStatus.repl_release: trio.Event` for requesting task\n'
|
||||||
f'|_{task}\n'
|
f'|_{task}\n'
|
||||||
)
|
)
|
||||||
|
@ -431,7 +361,7 @@ async def _pause(
|
||||||
'with no request ctx !?!?'
|
'with no request ctx !?!?'
|
||||||
)
|
)
|
||||||
|
|
||||||
log.devx(
|
log.debug(
|
||||||
f'attempting to {acq_prefix}acquire '
|
f'attempting to {acq_prefix}acquire '
|
||||||
f'{ctx_line}'
|
f'{ctx_line}'
|
||||||
)
|
)
|
||||||
|
@ -1022,6 +952,8 @@ def pause_from_sync(
|
||||||
# noop: non-cancelled `.to_thread`
|
# noop: non-cancelled `.to_thread`
|
||||||
# `trio.Cancelled`: cancelled `.to_thread`
|
# `trio.Cancelled`: cancelled `.to_thread`
|
||||||
|
|
||||||
|
# CASE: bg-thread spawned via `trio.to_thread`
|
||||||
|
# -----
|
||||||
# when called from a (bg) thread, run an async task in a new
|
# when called from a (bg) thread, run an async task in a new
|
||||||
# thread which will call `._pause()` manually with special
|
# thread which will call `._pause()` manually with special
|
||||||
# handling for root-actor caller usage.
|
# handling for root-actor caller usage.
|
||||||
|
@ -1107,6 +1039,9 @@ def pause_from_sync(
|
||||||
# '`tractor.pause[_from_sync]()` not yet supported '
|
# '`tractor.pause[_from_sync]()` not yet supported '
|
||||||
# 'for infected `asyncio` mode!'
|
# 'for infected `asyncio` mode!'
|
||||||
# )
|
# )
|
||||||
|
#
|
||||||
|
# CASE: bg-thread running `asyncio.Task`
|
||||||
|
# -----
|
||||||
elif (
|
elif (
|
||||||
not is_trio_thread
|
not is_trio_thread
|
||||||
and
|
and
|
||||||
|
@ -1184,7 +1119,9 @@ def pause_from_sync(
|
||||||
f'- greenback.bestow_portal(<task>)\n'
|
f'- greenback.bestow_portal(<task>)\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
else: # we are presumably the `trio.run()` + main thread
|
# CASE: `trio.run()` + "main thread"
|
||||||
|
# -----
|
||||||
|
else:
|
||||||
# raises on not-found by default
|
# raises on not-found by default
|
||||||
greenback: ModuleType = maybe_import_greenback()
|
greenback: ModuleType = maybe_import_greenback()
|
||||||
|
|
||||||
|
@ -1286,7 +1223,9 @@ def _sync_pause_from_builtin(
|
||||||
Proxy call `.pause_from_sync()` but indicate the caller is the
|
Proxy call `.pause_from_sync()` but indicate the caller is the
|
||||||
`breakpoint()` built-in.
|
`breakpoint()` built-in.
|
||||||
|
|
||||||
Note: this assigned to `os.environ['PYTHONBREAKPOINT']` inside `._root`
|
Note: this always assigned to `os.environ['PYTHONBREAKPOINT']`
|
||||||
|
inside `._root.open_root_actor()` whenever `debug_mode=True` is
|
||||||
|
set.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
pause_from_sync(
|
pause_from_sync(
|
||||||
|
|
|
@ -22,7 +22,9 @@ Root-actor TTY mutex-locking machinery.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import asyncio
|
import asyncio
|
||||||
from contextlib import (
|
from contextlib import (
|
||||||
|
AbstractContextManager,
|
||||||
asynccontextmanager as acm,
|
asynccontextmanager as acm,
|
||||||
|
ExitStack,
|
||||||
)
|
)
|
||||||
import textwrap
|
import textwrap
|
||||||
import threading
|
import threading
|
||||||
|
@ -75,6 +77,9 @@ if TYPE_CHECKING:
|
||||||
from ._repl import (
|
from ._repl import (
|
||||||
PdbREPL,
|
PdbREPL,
|
||||||
)
|
)
|
||||||
|
from ._post_mortem import (
|
||||||
|
BoxedMaybeException,
|
||||||
|
)
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
@ -601,6 +606,10 @@ class DebugStatus:
|
||||||
# request.
|
# request.
|
||||||
repl: PdbREPL|None = None
|
repl: PdbREPL|None = None
|
||||||
|
|
||||||
|
# any `repl_fixture` provided by user are entered and
|
||||||
|
# latered closed on `.release()`
|
||||||
|
_fixture_stack = ExitStack()
|
||||||
|
|
||||||
# TODO: yet again this looks like a task outcome where we need
|
# TODO: yet again this looks like a task outcome where we need
|
||||||
# to sync to the completion of one task (and get its result)
|
# to sync to the completion of one task (and get its result)
|
||||||
# being used everywhere for syncing..
|
# being used everywhere for syncing..
|
||||||
|
@ -803,6 +812,70 @@ class DebugStatus:
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# TODO, support @acm?
|
||||||
|
# -[ ] what about a return-proto for determining
|
||||||
|
# whether the REPL should be allowed to enage?
|
||||||
|
# -[x] consider factoring this `_repl_fixture` block into
|
||||||
|
# a common @cm somehow so it can be repurposed both here and
|
||||||
|
# in `._pause()`??
|
||||||
|
# -[ ] we could also use the `ContextDecorator`-type in that
|
||||||
|
# case to simply decorate the `_enter_repl_sync()` closure?
|
||||||
|
# |_https://docs.python.org/3/library/contextlib.html#using-a-context-manager-as-a-function-decorator
|
||||||
|
@classmethod
|
||||||
|
def maybe_enter_repl_fixture(
|
||||||
|
cls,
|
||||||
|
# ^XXX **always provided** by the low-level REPL-invoker,
|
||||||
|
# - _post_mortem()
|
||||||
|
# - _pause()
|
||||||
|
repl: PdbREPL,
|
||||||
|
|
||||||
|
# maybe pre/post REPL entry
|
||||||
|
repl_fixture: (
|
||||||
|
AbstractContextManager[bool]
|
||||||
|
|None
|
||||||
|
) = None,
|
||||||
|
|
||||||
|
# if called from crashed context, provided by
|
||||||
|
# `open_crash_handler()`
|
||||||
|
boxed_maybe_exc: BoxedMaybeException|None = None,
|
||||||
|
) -> bool:
|
||||||
|
'''
|
||||||
|
Maybe open a pre/post REPL entry "fixture" `@cm` provided by the
|
||||||
|
user, the caller should use the delivered `bool` to determine
|
||||||
|
whether to engage the `PdbREPL`.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if not (
|
||||||
|
repl_fixture
|
||||||
|
or
|
||||||
|
(rt_repl_fixture := _state._runtime_vars.get('repl_fixture'))
|
||||||
|
):
|
||||||
|
return True # YES always enter
|
||||||
|
|
||||||
|
_repl_fixture = (
|
||||||
|
repl_fixture
|
||||||
|
or
|
||||||
|
rt_repl_fixture
|
||||||
|
)
|
||||||
|
enter_repl: bool = DebugStatus._fixture_stack.enter_context(
|
||||||
|
_repl_fixture(
|
||||||
|
repl=repl,
|
||||||
|
maybe_bxerr=boxed_maybe_exc,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not enter_repl:
|
||||||
|
log.pdb(
|
||||||
|
f'pdbp-REPL blocked by a `repl_fixture()` which yielded `False` !\n'
|
||||||
|
f'repl_fixture: {repl_fixture}\n'
|
||||||
|
f'rt_repl_fixture: {rt_repl_fixture}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
log.devx(
|
||||||
|
f'User provided `repl_fixture` entered with,\n'
|
||||||
|
f'{repl_fixture!r} -> {enter_repl!r}\n'
|
||||||
|
)
|
||||||
|
return enter_repl
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
# @pdbp.hideframe
|
# @pdbp.hideframe
|
||||||
def release(
|
def release(
|
||||||
|
@ -890,6 +963,8 @@ class DebugStatus:
|
||||||
if current_actor(err_on_no_runtime=False):
|
if current_actor(err_on_no_runtime=False):
|
||||||
cls.unshield_sigint()
|
cls.unshield_sigint()
|
||||||
|
|
||||||
|
cls._fixture_stack.close()
|
||||||
|
|
||||||
|
|
||||||
# TODO: use the new `@lowlevel.singleton` for this!
|
# TODO: use the new `@lowlevel.singleton` for this!
|
||||||
def get_debug_req() -> DebugStatus|None:
|
def get_debug_req() -> DebugStatus|None:
|
||||||
|
|
Loading…
Reference in New Issue