Add initial `repl_fixture` support B)
It turns out to be fairly useful to allow hooking into a given actor's
entry-and-exit around `.devx._debug._pause/._post_mortem()` calls which
engage the `pdbp.Pdb` REPL (really our `._debug.PdbREPL` but yeah).
Some very handy use cases include,
- swapping out-of-band (config) state that may otherwise halt the
  user's app since the actor normally handles kb&mouse input, in thread,
  which means that the handler will be blocked while the REPL is in use.
- (remotely) reporting actor-runtime state for monitoring purposes
  around crashes or pauses in normal operation.
- allowing for crash-handling to be hard-disabled via
  `._state._runtime_vars` say for when you never want a debugger to be
  entered in a production instance where you're not-sure-if/don't-want
  per-actor `debug_mode: bool` settings to always be unset, say bc
  you're still debugging some edge cases that ow you'd normally want to
  REPL up.
Impl details,
- add a new optional `._state._runtime_vars['repl_fixture']` field which
  for now can be manually set; i saw no reason for a formal API yet
  since we want to convert the `dict` to a struct anyway (first).
- augment both `.devx._debug._pause()/._post_mortem()` with a new
  optional `repl_fixture: AbstractContextManager[bool]` kwarg which
  when provided is `with repl_fixture()` opened around the lowlevel
  REPL interaction calls; if the enter-result, an expected `bool`, is
  `False` then the interaction is hard-bypassed.
  * for the `._pause()` case the `@cm` is opened around the entire body
    of the embedded `_enter_repl_sync()` closure (for now) though
    ideally longer term this entire routine is factored to be a lot less
    "nested" Bp
  * in `_post_mortem()` the entire previous body is wrapped similarly
    and also now excepts an optional `boxed_maybe_exc: BoxedMaybeException`
    only passed in the `open_crash_handler()` caller case.
- when the new runtime-var is overridden, (only manually atm) it is used
  instead but only whenever the above `repl_fixture` kwarg is left null.
- add a `BoxedMaybeException.pformat() = __repr__()` which when
  a `.value: Exception` is set renders a more "objecty" repr of the exc.
Obviously tests for all this should be coming soon!
			
			
				repl_fixture
			
			
		
							parent
							
								
									c93a7d9b24
								
							
						
					
					
						commit
						5e102ec368
					
				| 
						 | 
					@ -48,8 +48,9 @@ _current_actor: Actor|None = None  # type: ignore # noqa
 | 
				
			||||||
_last_actor_terminated: Actor|None = None
 | 
					_last_actor_terminated: Actor|None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: mk this a `msgspec.Struct`!
 | 
					# TODO: mk this a `msgspec.Struct`!
 | 
				
			||||||
 | 
					# -[ ] type out all fields obvi!
 | 
				
			||||||
 | 
					# -[ ] (eventually) mk wire-ready for monitoring?
 | 
				
			||||||
_runtime_vars: dict[str, Any] = {
 | 
					_runtime_vars: dict[str, Any] = {
 | 
				
			||||||
    '_debug_mode': False,
 | 
					 | 
				
			||||||
    # root of actor-process tree info
 | 
					    # root of actor-process tree info
 | 
				
			||||||
    '_is_root': False,  # bool
 | 
					    '_is_root': False,  # bool
 | 
				
			||||||
    '_root_mailbox': (None, None),  # tuple[str|None, str|None]
 | 
					    '_root_mailbox': (None, None),  # tuple[str|None, str|None]
 | 
				
			||||||
| 
						 | 
					@ -61,10 +62,14 @@ _runtime_vars: dict[str, Any] = {
 | 
				
			||||||
    # registrar info
 | 
					    # registrar info
 | 
				
			||||||
    '_registry_addrs': [],
 | 
					    '_registry_addrs': [],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    '_is_infected_aio': False,
 | 
					    # `debug_mode: bool` settings
 | 
				
			||||||
 | 
					    '_debug_mode': False,  # bool
 | 
				
			||||||
 | 
					    'repl_fixture': False,  # |AbstractContextManager[bool]
 | 
				
			||||||
    # for `tractor.pause_from_sync()` & `breakpoint()` support
 | 
					    # for `tractor.pause_from_sync()` & `breakpoint()` support
 | 
				
			||||||
    'use_greenback': False,
 | 
					    'use_greenback': False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # infected-`asyncio`-mode: `trio` running as guest.
 | 
				
			||||||
 | 
					    '_is_infected_aio': False,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ from __future__ import annotations
 | 
				
			||||||
import asyncio
 | 
					import asyncio
 | 
				
			||||||
import bdb
 | 
					import bdb
 | 
				
			||||||
from contextlib import (
 | 
					from contextlib import (
 | 
				
			||||||
 | 
					    AbstractContextManager,
 | 
				
			||||||
    asynccontextmanager as acm,
 | 
					    asynccontextmanager as acm,
 | 
				
			||||||
    contextmanager as cm,
 | 
					    contextmanager as cm,
 | 
				
			||||||
    nullcontext,
 | 
					    nullcontext,
 | 
				
			||||||
| 
						 | 
					@ -1774,6 +1775,13 @@ async def _pause(
 | 
				
			||||||
        tuple[Task, PdbREPL],
 | 
					        tuple[Task, PdbREPL],
 | 
				
			||||||
        trio.Event
 | 
					        trio.Event
 | 
				
			||||||
    ] = trio.TASK_STATUS_IGNORED,
 | 
					    ] = trio.TASK_STATUS_IGNORED,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # maybe pre/post REPL entry
 | 
				
			||||||
 | 
					    repl_fixture: (
 | 
				
			||||||
 | 
					        AbstractContextManager[bool]
 | 
				
			||||||
 | 
					        |None
 | 
				
			||||||
 | 
					    ) = None,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    **debug_func_kwargs,
 | 
					    **debug_func_kwargs,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
) -> tuple[Task, PdbREPL]|None:
 | 
					) -> tuple[Task, PdbREPL]|None:
 | 
				
			||||||
| 
						 | 
					@ -1836,76 +1844,103 @@ async def _pause(
 | 
				
			||||||
        debug_func: partial[None],
 | 
					        debug_func: partial[None],
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        __tracebackhide__: bool = hide_tb
 | 
					        __tracebackhide__: bool = hide_tb
 | 
				
			||||||
        debug_func_name: str = (
 | 
					 | 
				
			||||||
            debug_func.func.__name__ if debug_func else 'None'
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # TODO: do we want to support using this **just** for the
 | 
					        # TODO, support @acm?
 | 
				
			||||||
        # locking / common code (prolly to help address #320)?
 | 
					        # -[ ] what about a return-proto for determining
 | 
				
			||||||
        task_status.started((task, repl))
 | 
					        #     whether the REPL should be allowed to enage?
 | 
				
			||||||
        try:
 | 
					        nonlocal repl_fixture
 | 
				
			||||||
            if debug_func:
 | 
					        if not (
 | 
				
			||||||
                # block here one (at the appropriate frame *up*) where
 | 
					            repl_fixture
 | 
				
			||||||
                # ``breakpoint()`` was awaited and begin handling stdio.
 | 
					            or
 | 
				
			||||||
                log.devx(
 | 
					            (rt_repl_fixture := _state._runtime_vars.get('repl_fixture'))
 | 
				
			||||||
                    'Entering sync world of the `pdb` REPL for task..\n'
 | 
					        ):
 | 
				
			||||||
                    f'{repl}\n'
 | 
					            repl_fixture = nullcontext(
 | 
				
			||||||
                    f'  |_{task}\n'
 | 
					                enter_result=True,
 | 
				
			||||||
                 )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # set local task on process-global state to avoid
 | 
					        _repl_fixture = repl_fixture or rt_repl_fixture
 | 
				
			||||||
                # recurrent entries/requests from the same
 | 
					        with _repl_fixture(maybe_bxerr=None) as enter_repl:
 | 
				
			||||||
                # actor-local task.
 | 
					
 | 
				
			||||||
                DebugStatus.repl_task = task
 | 
					            # XXX when the fixture doesn't allow it, skip
 | 
				
			||||||
                if repl:
 | 
					            # the crash-handler REPL and raise now!
 | 
				
			||||||
                    DebugStatus.repl = repl
 | 
					            if not enter_repl:
 | 
				
			||||||
                else:
 | 
					                log.pdb(
 | 
				
			||||||
                    log.error(
 | 
					                    f'pdbp-REPL blocked by a `repl_fixture()` which yielded `False` !\n'
 | 
				
			||||||
                        'No REPl instance set before entering `debug_func`?\n'
 | 
					                    f'repl_fixture: {repl_fixture}\n'
 | 
				
			||||||
                        f'{debug_func}\n'
 | 
					                    f'rt_repl_fixture: {rt_repl_fixture}\n'
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            debug_func_name: str = (
 | 
				
			||||||
 | 
					                debug_func.func.__name__ if debug_func else 'None'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # TODO: do we want to support using this **just** for the
 | 
				
			||||||
 | 
					            # locking / common code (prolly to help address #320)?
 | 
				
			||||||
 | 
					            task_status.started((task, repl))
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if debug_func:
 | 
				
			||||||
 | 
					                    # block here one (at the appropriate frame *up*) where
 | 
				
			||||||
 | 
					                    # ``breakpoint()`` was awaited and begin handling stdio.
 | 
				
			||||||
 | 
					                    log.devx(
 | 
				
			||||||
 | 
					                        'Entering sync world of the `pdb` REPL for task..\n'
 | 
				
			||||||
 | 
					                        f'{repl}\n'
 | 
				
			||||||
 | 
					                        f'  |_{task}\n'
 | 
				
			||||||
 | 
					                     )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # set local task on process-global state to avoid
 | 
				
			||||||
 | 
					                    # recurrent entries/requests from the same
 | 
				
			||||||
 | 
					                    # actor-local task.
 | 
				
			||||||
 | 
					                    DebugStatus.repl_task = task
 | 
				
			||||||
 | 
					                    if repl:
 | 
				
			||||||
 | 
					                        DebugStatus.repl = repl
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        log.error(
 | 
				
			||||||
 | 
					                            'No REPl instance set before entering `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,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # invoke the low-level REPL activation routine which itself
 | 
					                # TODO: maybe invert this logic and instead
 | 
				
			||||||
                # should call into a `Pdb.set_trace()` of some sort.
 | 
					                # do `assert debug_func is None` when
 | 
				
			||||||
                debug_func(
 | 
					                # `called_from_sync`?
 | 
				
			||||||
                    repl=repl,
 | 
					                else:
 | 
				
			||||||
                    hide_tb=hide_tb,
 | 
					                    if (
 | 
				
			||||||
                    **debug_func_kwargs,
 | 
					                        called_from_sync
 | 
				
			||||||
 | 
					                        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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # TODO: maybe invert this logic and instead
 | 
					            except BaseException:
 | 
				
			||||||
            # do `assert debug_func is None` when
 | 
					                __tracebackhide__: bool = False
 | 
				
			||||||
            # `called_from_sync`?
 | 
					                log.exception(
 | 
				
			||||||
            else:
 | 
					                    'Failed to invoke internal\n\n'
 | 
				
			||||||
                if (
 | 
					                    f'`debug_func = {debug_func_name}`\n'
 | 
				
			||||||
                    called_from_sync
 | 
					                )
 | 
				
			||||||
                    and
 | 
					                # NOTE: OW this is ONLY called from the
 | 
				
			||||||
                    not DebugStatus.is_main_trio_thread()
 | 
					                # `.set_continue/next` hooks!
 | 
				
			||||||
                ):
 | 
					                DebugStatus.release(cancel_req_task=True)
 | 
				
			||||||
                    assert called_from_bg_thread
 | 
					 | 
				
			||||||
                    assert DebugStatus.repl_task is not task
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return (task, repl)
 | 
					                raise
 | 
				
			||||||
 | 
					 | 
				
			||||||
        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.devx(
 | 
					    log.devx(
 | 
				
			||||||
        'Entering `._pause()` for requesting task\n'
 | 
					        'Entering `._pause()` for requesting task\n'
 | 
				
			||||||
| 
						 | 
					@ -2899,6 +2934,14 @@ def _post_mortem(
 | 
				
			||||||
    shield: bool = False,
 | 
					    shield: bool = False,
 | 
				
			||||||
    hide_tb: bool = False,
 | 
					    hide_tb: bool = False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # maybe pre/post REPL entry
 | 
				
			||||||
 | 
					    repl_fixture: (
 | 
				
			||||||
 | 
					        AbstractContextManager[bool]
 | 
				
			||||||
 | 
					        |None
 | 
				
			||||||
 | 
					    ) = None,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    boxed_maybe_exc: BoxedMaybeException|None = None,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    Enter the ``pdbpp`` port mortem entrypoint using our custom
 | 
					    Enter the ``pdbpp`` port mortem entrypoint using our custom
 | 
				
			||||||
| 
						 | 
					@ -2906,55 +2949,81 @@ def _post_mortem(
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    __tracebackhide__: bool = hide_tb
 | 
					    __tracebackhide__: bool = hide_tb
 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        actor: tractor.Actor = current_actor()
 | 
					 | 
				
			||||||
        actor_repr: str = str(actor.uid)
 | 
					 | 
				
			||||||
        # ^TODO, instead a nice runtime-info + maddr + uid?
 | 
					 | 
				
			||||||
        # -[ ] impl a `Actor.__repr()__`??
 | 
					 | 
				
			||||||
        #  |_ <task>:<thread> @ <actor>
 | 
					 | 
				
			||||||
        # no_runtime: bool = False
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    except NoRuntime:
 | 
					    # TODO, support @acm?
 | 
				
			||||||
        actor_repr: str = '<no-actor-runtime?>'
 | 
					    # -[ ] what about a return-proto for determining
 | 
				
			||||||
        # no_runtime: bool = True
 | 
					    #     whether the REPL should be allowed to enage?
 | 
				
			||||||
 | 
					    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)(maybe_bxerr=boxed_maybe_exc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try:
 | 
					    with _repl_fixture as enter_repl:
 | 
				
			||||||
        task_repr: Task = current_task()
 | 
					 | 
				
			||||||
    except RuntimeError:
 | 
					 | 
				
			||||||
        task_repr: str = '<unknown-Task>'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # TODO: print the actor supervion tree up to the root
 | 
					        # XXX when the fixture doesn't allow it, skip
 | 
				
			||||||
    # here! Bo
 | 
					        # the crash-handler REPL and raise now!
 | 
				
			||||||
    log.pdb(
 | 
					        if not enter_repl:
 | 
				
			||||||
        f'{_crash_msg}\n'
 | 
					            log.pdb(
 | 
				
			||||||
        f'x>(\n'
 | 
					                f'pdbp-REPL blocked by a `repl_fixture()` which yielded `False` !\n'
 | 
				
			||||||
        f' |_ {task_repr} @ {actor_repr}\n'
 | 
					                f'repl_fixture: {repl_fixture}\n'
 | 
				
			||||||
 | 
					                f'rt_repl_fixture: {rt_repl_fixture}\n'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    )
 | 
					        try:
 | 
				
			||||||
 | 
					            actor: tractor.Actor = current_actor()
 | 
				
			||||||
 | 
					            actor_repr: str = str(actor.uid)
 | 
				
			||||||
 | 
					            # ^TODO, instead a nice runtime-info + maddr + uid?
 | 
				
			||||||
 | 
					            # -[ ] impl a `Actor.__repr()__`??
 | 
				
			||||||
 | 
					            #  |_ <task>:<thread> @ <actor>
 | 
				
			||||||
 | 
					            # no_runtime: bool = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # NOTE only replacing this from `pdbp.xpm()` to add the
 | 
					        except NoRuntime:
 | 
				
			||||||
    # `end=''` to the print XD
 | 
					            actor_repr: str = '<no-actor-runtime?>'
 | 
				
			||||||
    print(traceback.format_exc(), end='')
 | 
					            # no_runtime: bool = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    caller_frame: FrameType = api_frame.f_back
 | 
					        try:
 | 
				
			||||||
 | 
					            task_repr: Task = current_task()
 | 
				
			||||||
 | 
					        except RuntimeError:
 | 
				
			||||||
 | 
					            task_repr: str = '<unknown-Task>'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # NOTE: see the impl details of followings to understand usage:
 | 
					        # TODO: print the actor supervion tree up to the root
 | 
				
			||||||
    # - `pdbp.post_mortem()`
 | 
					        # here! Bo
 | 
				
			||||||
    # - `pdbp.xps()`
 | 
					        log.pdb(
 | 
				
			||||||
    # - `bdb.interaction()`
 | 
					            f'{_crash_msg}\n'
 | 
				
			||||||
    repl.reset()
 | 
					            f'x>(\n'
 | 
				
			||||||
    repl.interaction(
 | 
					            f' |_ {task_repr} @ {actor_repr}\n'
 | 
				
			||||||
        frame=caller_frame,
 | 
					
 | 
				
			||||||
        # frame=None,
 | 
					        )
 | 
				
			||||||
        traceback=tb,
 | 
					
 | 
				
			||||||
    )
 | 
					        # NOTE only replacing this from `pdbp.xpm()` to add the
 | 
				
			||||||
    # XXX NOTE XXX: absolutely required to avoid hangs!
 | 
					        # `end=''` to the print XD
 | 
				
			||||||
    # Since we presume the post-mortem was enaged to a task-ending
 | 
					        print(traceback.format_exc(), end='')
 | 
				
			||||||
    # error, we MUST release the local REPL request so that not other
 | 
					        caller_frame: FrameType = api_frame.f_back
 | 
				
			||||||
    # local task nor the root remains blocked!
 | 
					
 | 
				
			||||||
    # if not no_runtime:
 | 
					        # NOTE: see the impl details of followings to understand usage:
 | 
				
			||||||
    #     DebugStatus.release()
 | 
					        # - `pdbp.post_mortem()`
 | 
				
			||||||
    DebugStatus.release()
 | 
					        # - `pdbp.xps()`
 | 
				
			||||||
 | 
					        # - `bdb.interaction()`
 | 
				
			||||||
 | 
					        repl.reset()
 | 
				
			||||||
 | 
					        repl.interaction(
 | 
				
			||||||
 | 
					            frame=caller_frame,
 | 
				
			||||||
 | 
					            # frame=None,
 | 
				
			||||||
 | 
					            traceback=tb,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        # XXX NOTE XXX: absolutely 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!
 | 
				
			||||||
 | 
					        # if not no_runtime:
 | 
				
			||||||
 | 
					        #     DebugStatus.release()
 | 
				
			||||||
 | 
					        DebugStatus.release()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async def post_mortem(
 | 
					async def post_mortem(
 | 
				
			||||||
| 
						 | 
					@ -3210,6 +3279,23 @@ class BoxedMaybeException(Struct):
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    value: BaseException|None = None
 | 
					    value: BaseException|None = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def pformat(self) -> str:
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        Repr the boxed `.value` error in more-than-string
 | 
				
			||||||
 | 
					        repr form.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        '''
 | 
				
			||||||
 | 
					        if not self.value:
 | 
				
			||||||
 | 
					            return f'<{type(self).__name__}( .value=None )>\n'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return (
 | 
				
			||||||
 | 
					            f'<{type(self.value).__name__}(\n'
 | 
				
			||||||
 | 
					            f' |_.value = {self.value}\n'
 | 
				
			||||||
 | 
					            f')>\n'
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    __repr__ = pformat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# TODO: better naming and what additionals?
 | 
					# TODO: better naming and what additionals?
 | 
				
			||||||
# - [ ] optional runtime plugging?
 | 
					# - [ ] optional runtime plugging?
 | 
				
			||||||
| 
						 | 
					@ -3226,7 +3312,12 @@ def open_crash_handler(
 | 
				
			||||||
        KeyboardInterrupt,
 | 
					        KeyboardInterrupt,
 | 
				
			||||||
        trio.Cancelled,
 | 
					        trio.Cancelled,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    tb_hide: bool = True,
 | 
					    tb_hide: bool = False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    repl_fixture: (
 | 
				
			||||||
 | 
					        AbstractContextManager[bool]  # pre/post REPL entry
 | 
				
			||||||
 | 
					        |None
 | 
				
			||||||
 | 
					    ) = None,
 | 
				
			||||||
):
 | 
					):
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
    Generic "post mortem" crash handler using `pdbp` REPL debugger.
 | 
					    Generic "post mortem" crash handler using `pdbp` REPL debugger.
 | 
				
			||||||
| 
						 | 
					@ -3266,6 +3357,9 @@ def open_crash_handler(
 | 
				
			||||||
                    repl=mk_pdb(),
 | 
					                    repl=mk_pdb(),
 | 
				
			||||||
                    tb=sys.exc_info()[2],
 | 
					                    tb=sys.exc_info()[2],
 | 
				
			||||||
                    api_frame=inspect.currentframe().f_back,
 | 
					                    api_frame=inspect.currentframe().f_back,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    repl_fixture=repl_fixture,
 | 
				
			||||||
 | 
					                    boxed_maybe_exc=boxed_maybe_exc,
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            except bdb.BdbQuit:
 | 
					            except bdb.BdbQuit:
 | 
				
			||||||
                __tracebackhide__: bool = False
 | 
					                __tracebackhide__: bool = False
 | 
				
			||||||
| 
						 | 
					@ -3281,7 +3375,7 @@ def open_crash_handler(
 | 
				
			||||||
@cm
 | 
					@cm
 | 
				
			||||||
def maybe_open_crash_handler(
 | 
					def maybe_open_crash_handler(
 | 
				
			||||||
    pdb: bool|None = None,
 | 
					    pdb: bool|None = None,
 | 
				
			||||||
    tb_hide: bool = True,
 | 
					    tb_hide: bool = False,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    **kwargs,
 | 
					    **kwargs,
 | 
				
			||||||
):
 | 
					):
 | 
				
			||||||
| 
						 | 
					@ -3293,11 +3387,11 @@ def maybe_open_crash_handler(
 | 
				
			||||||
    flag is passed the pdb REPL is engaed on any crashes B)
 | 
					    flag is passed the pdb REPL is engaed on any crashes B)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    '''
 | 
					    '''
 | 
				
			||||||
 | 
					    __tracebackhide__: bool = tb_hide
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if pdb is None:
 | 
					    if pdb is None:
 | 
				
			||||||
        pdb: bool = _state.is_debug_mode()
 | 
					        pdb: bool = _state.is_debug_mode()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    __tracebackhide__: bool = tb_hide
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    rtctx = nullcontext(
 | 
					    rtctx = nullcontext(
 | 
				
			||||||
        enter_result=BoxedMaybeException()
 | 
					        enter_result=BoxedMaybeException()
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue