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
							
								
									ce2d06cad9
								
							
						
					
					
						commit
						a5126862b9
					
				| 
						 | 
					@ -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