Fix frame-selection display on first REPL entry
For whatever reason pdb(p), and in general, will show the frame of the *next* python instruction/LOC on initial entry (at least using `.set_trace()`), as such remove the `try/finally` block in the sync code entrypoint `.pause_from_sync()`, and also since doesn't seem like we really need it anyway. Further, and to this end: - enable hidden frames support in our default config. - fix/drop/mask all the frame ref-ing/mangling we had prior since it's no longer needed as well as manual `Lock` releasing which seems to work already by having the `greenback` spawned task do it's normal thing? - move to no `Union` type annots. - hide all frames that can add "this is the runtime confusion" to traces.asyncio_debugger_support
parent
98a7326c85
commit
4ace8f6037
|
@ -30,7 +30,6 @@ from functools import (
|
||||||
from contextlib import asynccontextmanager as acm
|
from contextlib import asynccontextmanager as acm
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
Optional,
|
|
||||||
Callable,
|
Callable,
|
||||||
AsyncIterator,
|
AsyncIterator,
|
||||||
AsyncGenerator,
|
AsyncGenerator,
|
||||||
|
@ -40,7 +39,10 @@ from types import FrameType
|
||||||
import pdbp
|
import pdbp
|
||||||
import tractor
|
import tractor
|
||||||
import trio
|
import trio
|
||||||
from trio_typing import TaskStatus
|
from trio_typing import (
|
||||||
|
TaskStatus,
|
||||||
|
# Task,
|
||||||
|
)
|
||||||
|
|
||||||
from .log import get_logger
|
from .log import get_logger
|
||||||
from ._discovery import get_root
|
from ._discovery import get_root
|
||||||
|
@ -69,10 +71,10 @@ class Lock:
|
||||||
'''
|
'''
|
||||||
repl: MultiActorPdb | None = None
|
repl: MultiActorPdb | None = None
|
||||||
# placeholder for function to set a ``trio.Event`` on debugger exit
|
# placeholder for function to set a ``trio.Event`` on debugger exit
|
||||||
# pdb_release_hook: Optional[Callable] = None
|
# pdb_release_hook: Callable | None = None
|
||||||
|
|
||||||
_trio_handler: Callable[
|
_trio_handler: Callable[
|
||||||
[int, Optional[FrameType]], Any
|
[int, FrameType | None], Any
|
||||||
] | int | None = None
|
] | int | None = None
|
||||||
|
|
||||||
# actor-wide variable pointing to current task name using debugger
|
# actor-wide variable pointing to current task name using debugger
|
||||||
|
@ -83,23 +85,23 @@ class Lock:
|
||||||
# and must be cancelled if this actor is cancelled via IPC
|
# and must be cancelled if this actor is cancelled via IPC
|
||||||
# request-message otherwise deadlocks with the parent actor may
|
# request-message otherwise deadlocks with the parent actor may
|
||||||
# ensure
|
# ensure
|
||||||
_debugger_request_cs: Optional[trio.CancelScope] = None
|
_debugger_request_cs: trio.CancelScope | None = None
|
||||||
|
|
||||||
# NOTE: set only in the root actor for the **local** root spawned task
|
# NOTE: set only in the root actor for the **local** root spawned task
|
||||||
# which has acquired the lock (i.e. this is on the callee side of
|
# which has acquired the lock (i.e. this is on the callee side of
|
||||||
# the `lock_tty_for_child()` context entry).
|
# the `lock_tty_for_child()` context entry).
|
||||||
_root_local_task_cs_in_debug: Optional[trio.CancelScope] = None
|
_root_local_task_cs_in_debug: trio.CancelScope | None = None
|
||||||
|
|
||||||
# actor tree-wide actor uid that supposedly has the tty lock
|
# actor tree-wide actor uid that supposedly has the tty lock
|
||||||
global_actor_in_debug: Optional[tuple[str, str]] = None
|
global_actor_in_debug: tuple[str, str] = None
|
||||||
|
|
||||||
local_pdb_complete: Optional[trio.Event] = None
|
local_pdb_complete: trio.Event | None = None
|
||||||
no_remote_has_tty: Optional[trio.Event] = None
|
no_remote_has_tty: trio.Event | None = None
|
||||||
|
|
||||||
# lock in root actor preventing multi-access to local tty
|
# lock in root actor preventing multi-access to local tty
|
||||||
_debug_lock: trio.StrictFIFOLock = trio.StrictFIFOLock()
|
_debug_lock: trio.StrictFIFOLock = trio.StrictFIFOLock()
|
||||||
|
|
||||||
_orig_sigint_handler: Optional[Callable] = None
|
_orig_sigint_handler: Callable | None = None
|
||||||
_blocked: set[tuple[str, str]] = set()
|
_blocked: set[tuple[str, str]] = set()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -110,6 +112,7 @@ class Lock:
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@pdbp.hideframe # XXX NOTE XXX see below in `.pause_from_sync()`
|
||||||
def unshield_sigint(cls):
|
def unshield_sigint(cls):
|
||||||
# always restore ``trio``'s sigint handler. see notes below in
|
# always restore ``trio``'s sigint handler. see notes below in
|
||||||
# the pdb factory about the nightmare that is that code swapping
|
# the pdb factory about the nightmare that is that code swapping
|
||||||
|
@ -129,10 +132,6 @@ class Lock:
|
||||||
if owner:
|
if owner:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# actor-local state, irrelevant for non-root.
|
|
||||||
cls.global_actor_in_debug = None
|
|
||||||
cls.local_task_in_debug = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# sometimes the ``trio`` might already be terminated in
|
# sometimes the ``trio`` might already be terminated in
|
||||||
# which case this call will raise.
|
# which case this call will raise.
|
||||||
|
@ -143,6 +142,11 @@ class Lock:
|
||||||
cls.unshield_sigint()
|
cls.unshield_sigint()
|
||||||
cls.repl = None
|
cls.repl = None
|
||||||
|
|
||||||
|
# actor-local state, irrelevant for non-root.
|
||||||
|
cls.global_actor_in_debug = None
|
||||||
|
cls.local_task_in_debug = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TractorConfig(pdbp.DefaultConfig):
|
class TractorConfig(pdbp.DefaultConfig):
|
||||||
'''
|
'''
|
||||||
|
@ -151,7 +155,7 @@ class TractorConfig(pdbp.DefaultConfig):
|
||||||
'''
|
'''
|
||||||
use_pygments: bool = True
|
use_pygments: bool = True
|
||||||
sticky_by_default: bool = False
|
sticky_by_default: bool = False
|
||||||
enable_hidden_frames: bool = False
|
enable_hidden_frames: bool = True
|
||||||
|
|
||||||
# much thanks @mdmintz for the hot tip!
|
# much thanks @mdmintz for the hot tip!
|
||||||
# fixes line spacing issue when resizing terminal B)
|
# fixes line spacing issue when resizing terminal B)
|
||||||
|
@ -228,26 +232,23 @@ async def _acquire_debug_lock_from_root_task(
|
||||||
to the ``pdb`` repl.
|
to the ``pdb`` repl.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
task_name = trio.lowlevel.current_task().name
|
task_name: str = trio.lowlevel.current_task().name
|
||||||
|
we_acquired: bool = False
|
||||||
|
|
||||||
log.runtime(
|
log.runtime(
|
||||||
f"Attempting to acquire TTY lock, remote task: {task_name}:{uid}"
|
f"Attempting to acquire TTY lock, remote task: {task_name}:{uid}"
|
||||||
)
|
)
|
||||||
|
|
||||||
we_acquired = False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.runtime(
|
log.runtime(
|
||||||
f"entering lock checkpoint, remote task: {task_name}:{uid}"
|
f"entering lock checkpoint, remote task: {task_name}:{uid}"
|
||||||
)
|
)
|
||||||
we_acquired = True
|
|
||||||
|
|
||||||
# NOTE: if the surrounding cancel scope from the
|
# NOTE: if the surrounding cancel scope from the
|
||||||
# `lock_tty_for_child()` caller is cancelled, this line should
|
# `lock_tty_for_child()` caller is cancelled, this line should
|
||||||
# unblock and NOT leave us in some kind of
|
# unblock and NOT leave us in some kind of
|
||||||
# a "child-locked-TTY-but-child-is-uncontactable-over-IPC"
|
# a "child-locked-TTY-but-child-is-uncontactable-over-IPC"
|
||||||
# condition.
|
# condition.
|
||||||
await Lock._debug_lock.acquire()
|
await Lock._debug_lock.acquire()
|
||||||
|
we_acquired = True
|
||||||
|
|
||||||
if Lock.no_remote_has_tty is None:
|
if Lock.no_remote_has_tty is None:
|
||||||
# mark the tty lock as being in use so that the runtime
|
# mark the tty lock as being in use so that the runtime
|
||||||
|
@ -573,13 +574,15 @@ async def _pause(
|
||||||
try:
|
try:
|
||||||
# breakpoint()
|
# breakpoint()
|
||||||
if debug_func is None:
|
if debug_func is None:
|
||||||
assert release_lock_signal, (
|
# assert release_lock_signal, (
|
||||||
'Must pass `release_lock_signal: trio.Event` if no '
|
# 'Must pass `release_lock_signal: trio.Event` if no '
|
||||||
'trace func provided!'
|
# 'trace func provided!'
|
||||||
)
|
# )
|
||||||
print(f"{actor.uid} ENTERING WAIT")
|
print(f"{actor.uid} ENTERING WAIT")
|
||||||
task_status.started()
|
task_status.started()
|
||||||
await release_lock_signal.wait()
|
|
||||||
|
# with trio.CancelScope(shield=True):
|
||||||
|
# await release_lock_signal.wait()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# block here one (at the appropriate frame *up*) where
|
# block here one (at the appropriate frame *up*) where
|
||||||
|
@ -606,7 +609,7 @@ async def _pause(
|
||||||
def shield_sigint_handler(
|
def shield_sigint_handler(
|
||||||
signum: int,
|
signum: int,
|
||||||
frame: 'frame', # type: ignore # noqa
|
frame: 'frame', # type: ignore # noqa
|
||||||
# pdb_obj: Optional[MultiActorPdb] = None,
|
# pdb_obj: MultiActorPdb | None = None,
|
||||||
*args,
|
*args,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -620,7 +623,7 @@ def shield_sigint_handler(
|
||||||
'''
|
'''
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
|
|
||||||
uid_in_debug = Lock.global_actor_in_debug
|
uid_in_debug: tuple[str, str] | None = Lock.global_actor_in_debug
|
||||||
|
|
||||||
actor = tractor.current_actor()
|
actor = tractor.current_actor()
|
||||||
# print(f'{actor.uid} in HANDLER with ')
|
# print(f'{actor.uid} in HANDLER with ')
|
||||||
|
@ -638,14 +641,14 @@ def shield_sigint_handler(
|
||||||
else:
|
else:
|
||||||
raise KeyboardInterrupt
|
raise KeyboardInterrupt
|
||||||
|
|
||||||
any_connected = False
|
any_connected: bool = False
|
||||||
|
|
||||||
if uid_in_debug is not None:
|
if uid_in_debug is not None:
|
||||||
# try to see if the supposed (sub)actor in debug still
|
# try to see if the supposed (sub)actor in debug still
|
||||||
# has an active connection to *this* actor, and if not
|
# has an active connection to *this* actor, and if not
|
||||||
# it's likely they aren't using the TTY lock / debugger
|
# it's likely they aren't using the TTY lock / debugger
|
||||||
# and we should propagate SIGINT normally.
|
# and we should propagate SIGINT normally.
|
||||||
chans = actor._peers.get(tuple(uid_in_debug))
|
chans: list[tractor.Channel] = actor._peers.get(tuple(uid_in_debug))
|
||||||
if chans:
|
if chans:
|
||||||
any_connected = any(chan.connected() for chan in chans)
|
any_connected = any(chan.connected() for chan in chans)
|
||||||
if not any_connected:
|
if not any_connected:
|
||||||
|
@ -658,7 +661,7 @@ def shield_sigint_handler(
|
||||||
return do_cancel()
|
return do_cancel()
|
||||||
|
|
||||||
# only set in the actor actually running the REPL
|
# only set in the actor actually running the REPL
|
||||||
pdb_obj = Lock.repl
|
pdb_obj: MultiActorPdb | None = Lock.repl
|
||||||
|
|
||||||
# root actor branch that reports whether or not a child
|
# root actor branch that reports whether or not a child
|
||||||
# has locked debugger.
|
# has locked debugger.
|
||||||
|
@ -716,7 +719,7 @@ def shield_sigint_handler(
|
||||||
)
|
)
|
||||||
return do_cancel()
|
return do_cancel()
|
||||||
|
|
||||||
task = Lock.local_task_in_debug
|
task: str | None = Lock.local_task_in_debug
|
||||||
if (
|
if (
|
||||||
task
|
task
|
||||||
and pdb_obj
|
and pdb_obj
|
||||||
|
@ -791,15 +794,18 @@ def _set_trace(
|
||||||
Lock.local_task_in_debug = 'sync'
|
Lock.local_task_in_debug = 'sync'
|
||||||
|
|
||||||
pdb.set_trace(frame=frame)
|
pdb.set_trace(frame=frame)
|
||||||
|
# undo_
|
||||||
|
|
||||||
|
|
||||||
# TODO: allow pausing from sync code, normally by remapping
|
# TODO: allow pausing from sync code, normally by remapping
|
||||||
# python's builtin breakpoint() hook to this runtime aware version.
|
# python's builtin breakpoint() hook to this runtime aware version.
|
||||||
def pause_from_sync() -> None:
|
def pause_from_sync() -> None:
|
||||||
|
print("ENTER SYNC PAUSE")
|
||||||
import greenback
|
import greenback
|
||||||
|
__tracebackhide__ = True
|
||||||
|
|
||||||
actor: tractor.Actor = tractor.current_actor()
|
actor: tractor.Actor = tractor.current_actor()
|
||||||
task_can_release_tty_lock = trio.Event()
|
# task_can_release_tty_lock = trio.Event()
|
||||||
|
|
||||||
# spawn bg task which will lock out the TTY, we poll
|
# spawn bg task which will lock out the TTY, we poll
|
||||||
# just below until the release event is reporting that task as
|
# just below until the release event is reporting that task as
|
||||||
|
@ -808,34 +814,39 @@ def pause_from_sync() -> None:
|
||||||
actor._service_n.start(partial(
|
actor._service_n.start(partial(
|
||||||
_pause,
|
_pause,
|
||||||
debug_func=None,
|
debug_func=None,
|
||||||
release_lock_signal=task_can_release_tty_lock,
|
# release_lock_signal=task_can_release_tty_lock,
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
print("ENTER SYNC PAUSE")
|
|
||||||
pdb, undo_sigint = mk_mpdb()
|
db, undo_sigint = mk_mpdb()
|
||||||
try:
|
Lock.local_task_in_debug = 'sync'
|
||||||
print("ENTER SYNC PAUSE")
|
# db.config.enable_hidden_frames = True
|
||||||
# _set_trace(actor=actor)
|
|
||||||
|
|
||||||
# we entered the global ``breakpoint()`` built-in from sync
|
# we entered the global ``breakpoint()`` built-in from sync
|
||||||
# code?
|
# code?
|
||||||
Lock.local_task_in_debug = 'sync'
|
|
||||||
frame: FrameType | None = sys._getframe()
|
frame: FrameType | None = sys._getframe()
|
||||||
print(f'FRAME: {str(frame)}')
|
# print(f'FRAME: {str(frame)}')
|
||||||
|
# assert not db._is_hidden(frame)
|
||||||
|
|
||||||
frame: FrameType = frame.f_back # type: ignore
|
frame: FrameType = frame.f_back # type: ignore
|
||||||
print(f'FRAME: {str(frame)}')
|
# print(f'FRAME: {str(frame)}')
|
||||||
|
# if not db._is_hidden(frame):
|
||||||
|
# pdbp.set_trace()
|
||||||
|
# db._hidden_frames.append(
|
||||||
|
# (frame, frame.f_lineno)
|
||||||
|
# )
|
||||||
|
db.set_trace(frame=frame)
|
||||||
|
# NOTE XXX: see the `@pdbp.hideframe` decoration
|
||||||
|
# on `Lock.unshield_sigint()`.. I have NO CLUE why
|
||||||
|
# the next instruction's def frame is being shown
|
||||||
|
# in the tb but it seems to be something wonky with
|
||||||
|
# the way `pdb` core works?
|
||||||
|
# undo_sigint()
|
||||||
|
|
||||||
frame: FrameType = frame.f_back # type: ignore
|
# Lock.global_actor_in_debug = actor.uid
|
||||||
print(f'FRAME: {str(frame)}')
|
# Lock.release()
|
||||||
|
# task_can_release_tty_lock.set()
|
||||||
|
|
||||||
pdb.set_trace(frame=frame)
|
|
||||||
# pdb.do_frame(
|
|
||||||
# pdb.curindex
|
|
||||||
|
|
||||||
finally:
|
|
||||||
task_can_release_tty_lock.set()
|
|
||||||
undo_sigint()
|
|
||||||
|
|
||||||
# using the "pause" semantics instead since
|
# using the "pause" semantics instead since
|
||||||
# that better covers actually somewhat "pausing the runtime"
|
# that better covers actually somewhat "pausing the runtime"
|
||||||
|
@ -959,8 +970,7 @@ async def maybe_wait_for_debugger(
|
||||||
# will make the pdb repl unusable.
|
# will make the pdb repl unusable.
|
||||||
# Instead try to wait for pdb to be released before
|
# Instead try to wait for pdb to be released before
|
||||||
# tearing down.
|
# tearing down.
|
||||||
|
sub_in_debug: tuple[str, str] | None = None
|
||||||
sub_in_debug = None
|
|
||||||
|
|
||||||
for _ in range(poll_steps):
|
for _ in range(poll_steps):
|
||||||
|
|
||||||
|
@ -980,13 +990,15 @@ async def maybe_wait_for_debugger(
|
||||||
|
|
||||||
debug_complete = Lock.no_remote_has_tty
|
debug_complete = Lock.no_remote_has_tty
|
||||||
if (
|
if (
|
||||||
(debug_complete and
|
debug_complete
|
||||||
not debug_complete.is_set())
|
and sub_in_debug is not None
|
||||||
|
and not debug_complete.is_set()
|
||||||
):
|
):
|
||||||
log.debug(
|
log.pdb(
|
||||||
'Root has errored but pdb is in use by '
|
'Root has errored but pdb is in use by '
|
||||||
f'child {sub_in_debug}\n'
|
f'child {sub_in_debug}\n'
|
||||||
'Waiting on tty lock to release..')
|
'Waiting on tty lock to release..'
|
||||||
|
)
|
||||||
|
|
||||||
await debug_complete.wait()
|
await debug_complete.wait()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue