forked from goodboy/tractor
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 typing import (
|
||||
Any,
|
||||
Optional,
|
||||
Callable,
|
||||
AsyncIterator,
|
||||
AsyncGenerator,
|
||||
|
@ -40,7 +39,10 @@ from types import FrameType
|
|||
import pdbp
|
||||
import tractor
|
||||
import trio
|
||||
from trio_typing import TaskStatus
|
||||
from trio_typing import (
|
||||
TaskStatus,
|
||||
# Task,
|
||||
)
|
||||
|
||||
from .log import get_logger
|
||||
from ._discovery import get_root
|
||||
|
@ -69,10 +71,10 @@ class Lock:
|
|||
'''
|
||||
repl: MultiActorPdb | None = None
|
||||
# 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[
|
||||
[int, Optional[FrameType]], Any
|
||||
[int, FrameType | None], Any
|
||||
] | int | None = None
|
||||
|
||||
# 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
|
||||
# request-message otherwise deadlocks with the parent actor may
|
||||
# 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
|
||||
# which has acquired the lock (i.e. this is on the callee side of
|
||||
# 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
|
||||
global_actor_in_debug: Optional[tuple[str, str]] = None
|
||||
global_actor_in_debug: tuple[str, str] = None
|
||||
|
||||
local_pdb_complete: Optional[trio.Event] = None
|
||||
no_remote_has_tty: Optional[trio.Event] = None
|
||||
local_pdb_complete: trio.Event | None = None
|
||||
no_remote_has_tty: trio.Event | None = None
|
||||
|
||||
# lock in root actor preventing multi-access to local tty
|
||||
_debug_lock: trio.StrictFIFOLock = trio.StrictFIFOLock()
|
||||
|
||||
_orig_sigint_handler: Optional[Callable] = None
|
||||
_orig_sigint_handler: Callable | None = None
|
||||
_blocked: set[tuple[str, str]] = set()
|
||||
|
||||
@classmethod
|
||||
|
@ -110,6 +112,7 @@ class Lock:
|
|||
)
|
||||
|
||||
@classmethod
|
||||
@pdbp.hideframe # XXX NOTE XXX see below in `.pause_from_sync()`
|
||||
def unshield_sigint(cls):
|
||||
# always restore ``trio``'s sigint handler. see notes below in
|
||||
# the pdb factory about the nightmare that is that code swapping
|
||||
|
@ -129,10 +132,6 @@ class Lock:
|
|||
if owner:
|
||||
raise
|
||||
|
||||
# actor-local state, irrelevant for non-root.
|
||||
cls.global_actor_in_debug = None
|
||||
cls.local_task_in_debug = None
|
||||
|
||||
try:
|
||||
# sometimes the ``trio`` might already be terminated in
|
||||
# which case this call will raise.
|
||||
|
@ -143,6 +142,11 @@ class Lock:
|
|||
cls.unshield_sigint()
|
||||
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):
|
||||
'''
|
||||
|
@ -151,7 +155,7 @@ class TractorConfig(pdbp.DefaultConfig):
|
|||
'''
|
||||
use_pygments: bool = True
|
||||
sticky_by_default: bool = False
|
||||
enable_hidden_frames: bool = False
|
||||
enable_hidden_frames: bool = True
|
||||
|
||||
# much thanks @mdmintz for the hot tip!
|
||||
# fixes line spacing issue when resizing terminal B)
|
||||
|
@ -228,26 +232,23 @@ async def _acquire_debug_lock_from_root_task(
|
|||
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(
|
||||
f"Attempting to acquire TTY lock, remote task: {task_name}:{uid}"
|
||||
)
|
||||
|
||||
we_acquired = False
|
||||
|
||||
try:
|
||||
log.runtime(
|
||||
f"entering lock checkpoint, remote task: {task_name}:{uid}"
|
||||
)
|
||||
we_acquired = True
|
||||
|
||||
# NOTE: if the surrounding cancel scope from the
|
||||
# `lock_tty_for_child()` caller is cancelled, this line should
|
||||
# unblock and NOT leave us in some kind of
|
||||
# a "child-locked-TTY-but-child-is-uncontactable-over-IPC"
|
||||
# condition.
|
||||
await Lock._debug_lock.acquire()
|
||||
we_acquired = True
|
||||
|
||||
if Lock.no_remote_has_tty is None:
|
||||
# mark the tty lock as being in use so that the runtime
|
||||
|
@ -573,13 +574,15 @@ async def _pause(
|
|||
try:
|
||||
# breakpoint()
|
||||
if debug_func is None:
|
||||
assert release_lock_signal, (
|
||||
'Must pass `release_lock_signal: trio.Event` if no '
|
||||
'trace func provided!'
|
||||
)
|
||||
# assert release_lock_signal, (
|
||||
# 'Must pass `release_lock_signal: trio.Event` if no '
|
||||
# 'trace func provided!'
|
||||
# )
|
||||
print(f"{actor.uid} ENTERING WAIT")
|
||||
task_status.started()
|
||||
await release_lock_signal.wait()
|
||||
|
||||
# with trio.CancelScope(shield=True):
|
||||
# await release_lock_signal.wait()
|
||||
|
||||
else:
|
||||
# block here one (at the appropriate frame *up*) where
|
||||
|
@ -606,7 +609,7 @@ async def _pause(
|
|||
def shield_sigint_handler(
|
||||
signum: int,
|
||||
frame: 'frame', # type: ignore # noqa
|
||||
# pdb_obj: Optional[MultiActorPdb] = None,
|
||||
# pdb_obj: MultiActorPdb | None = None,
|
||||
*args,
|
||||
|
||||
) -> None:
|
||||
|
@ -620,7 +623,7 @@ def shield_sigint_handler(
|
|||
'''
|
||||
__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()
|
||||
# print(f'{actor.uid} in HANDLER with ')
|
||||
|
@ -638,14 +641,14 @@ def shield_sigint_handler(
|
|||
else:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
any_connected = False
|
||||
any_connected: bool = False
|
||||
|
||||
if uid_in_debug is not None:
|
||||
# try to see if the supposed (sub)actor in debug still
|
||||
# has an active connection to *this* actor, and if not
|
||||
# it's likely they aren't using the TTY lock / debugger
|
||||
# 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:
|
||||
any_connected = any(chan.connected() for chan in chans)
|
||||
if not any_connected:
|
||||
|
@ -658,7 +661,7 @@ def shield_sigint_handler(
|
|||
return do_cancel()
|
||||
|
||||
# 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
|
||||
# has locked debugger.
|
||||
|
@ -716,7 +719,7 @@ def shield_sigint_handler(
|
|||
)
|
||||
return do_cancel()
|
||||
|
||||
task = Lock.local_task_in_debug
|
||||
task: str | None = Lock.local_task_in_debug
|
||||
if (
|
||||
task
|
||||
and pdb_obj
|
||||
|
@ -791,15 +794,18 @@ def _set_trace(
|
|||
Lock.local_task_in_debug = 'sync'
|
||||
|
||||
pdb.set_trace(frame=frame)
|
||||
# undo_
|
||||
|
||||
|
||||
# TODO: allow pausing from sync code, normally by remapping
|
||||
# python's builtin breakpoint() hook to this runtime aware version.
|
||||
def pause_from_sync() -> None:
|
||||
print("ENTER SYNC PAUSE")
|
||||
import greenback
|
||||
__tracebackhide__ = True
|
||||
|
||||
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
|
||||
# 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(
|
||||
_pause,
|
||||
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()
|
||||
try:
|
||||
print("ENTER SYNC PAUSE")
|
||||
# _set_trace(actor=actor)
|
||||
|
||||
db, undo_sigint = mk_mpdb()
|
||||
Lock.local_task_in_debug = 'sync'
|
||||
# db.config.enable_hidden_frames = True
|
||||
|
||||
# we entered the global ``breakpoint()`` built-in from sync
|
||||
# code?
|
||||
Lock.local_task_in_debug = 'sync'
|
||||
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
|
||||
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
|
||||
print(f'FRAME: {str(frame)}')
|
||||
# Lock.global_actor_in_debug = actor.uid
|
||||
# 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
|
||||
# that better covers actually somewhat "pausing the runtime"
|
||||
|
@ -959,8 +970,7 @@ async def maybe_wait_for_debugger(
|
|||
# will make the pdb repl unusable.
|
||||
# Instead try to wait for pdb to be released before
|
||||
# tearing down.
|
||||
|
||||
sub_in_debug = None
|
||||
sub_in_debug: tuple[str, str] | None = None
|
||||
|
||||
for _ in range(poll_steps):
|
||||
|
||||
|
@ -980,13 +990,15 @@ async def maybe_wait_for_debugger(
|
|||
|
||||
debug_complete = Lock.no_remote_has_tty
|
||||
if (
|
||||
(debug_complete and
|
||||
not debug_complete.is_set())
|
||||
debug_complete
|
||||
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 '
|
||||
f'child {sub_in_debug}\n'
|
||||
'Waiting on tty lock to release..')
|
||||
'Waiting on tty lock to release..'
|
||||
)
|
||||
|
||||
await debug_complete.wait()
|
||||
|
||||
|
|
Loading…
Reference in New Issue