forked from goodboy/tractor
Simplify all hooks to a common `Lock.release()`
parent
65540f3e2a
commit
8f1fe2376a
|
@ -68,7 +68,7 @@ class Lock:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# 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: Optional[Callable] = None
|
||||||
|
|
||||||
# actor-wide variable pointing to current task name using debugger
|
# actor-wide variable pointing to current task name using debugger
|
||||||
local_task_in_debug: Optional[str] = None
|
local_task_in_debug: Optional[str] = None
|
||||||
|
@ -108,13 +108,7 @@ class Lock:
|
||||||
cls._orig_sigint_handler = None
|
cls._orig_sigint_handler = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def maybe_release(cls):
|
def release(cls):
|
||||||
cls.local_task_in_debug = None
|
|
||||||
if cls.pdb_release_hook:
|
|
||||||
cls.pdb_release_hook()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def root_release(cls):
|
|
||||||
try:
|
try:
|
||||||
cls._debug_lock.release()
|
cls._debug_lock.release()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
@ -125,6 +119,7 @@ class Lock:
|
||||||
if owner:
|
if owner:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
# actor-local state, irrelevant for non-root.
|
||||||
cls.global_actor_in_debug = None
|
cls.global_actor_in_debug = None
|
||||||
cls.local_task_in_debug = None
|
cls.local_task_in_debug = None
|
||||||
|
|
||||||
|
@ -165,17 +160,17 @@ class MultiActorPdb(pdbpp.Pdb):
|
||||||
try:
|
try:
|
||||||
super().set_continue()
|
super().set_continue()
|
||||||
finally:
|
finally:
|
||||||
Lock.maybe_release()
|
Lock.release()
|
||||||
|
|
||||||
def set_quit(self):
|
def set_quit(self):
|
||||||
try:
|
try:
|
||||||
super().set_quit()
|
super().set_quit()
|
||||||
finally:
|
finally:
|
||||||
Lock.maybe_release()
|
Lock.release()
|
||||||
|
|
||||||
|
|
||||||
@acm
|
@acm
|
||||||
async def _acquire_debug_lock(
|
async def _acquire_debug_lock_from_root_task(
|
||||||
uid: Tuple[str, str]
|
uid: Tuple[str, str]
|
||||||
|
|
||||||
) -> AsyncIterator[trio.StrictFIFOLock]:
|
) -> AsyncIterator[trio.StrictFIFOLock]:
|
||||||
|
@ -217,7 +212,7 @@ async def _acquire_debug_lock(
|
||||||
# IF we received a cancel during the shielded lock entry of some
|
# IF we received a cancel during the shielded lock entry of some
|
||||||
# next-in-queue requesting task, then the resumption here will
|
# next-in-queue requesting task, then the resumption here will
|
||||||
# result in that ``trio.Cancelled`` being raised to our caller
|
# result in that ``trio.Cancelled`` being raised to our caller
|
||||||
# (likely from ``_hijack_stdin_for_child()`` below)! In
|
# (likely from ``lock_tty_for_child()`` below)! In
|
||||||
# this case the ``finally:`` below should trigger and the
|
# this case the ``finally:`` below should trigger and the
|
||||||
# surrounding caller side context should cancel normally
|
# surrounding caller side context should cancel normally
|
||||||
# relaying back to the caller.
|
# relaying back to the caller.
|
||||||
|
@ -225,8 +220,6 @@ async def _acquire_debug_lock(
|
||||||
yield Lock._debug_lock
|
yield Lock._debug_lock
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
# if Lock.global_actor_in_debug == uid:
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
we_acquired
|
we_acquired
|
||||||
and Lock._debug_lock.locked()
|
and Lock._debug_lock.locked()
|
||||||
|
@ -255,20 +248,21 @@ async def _acquire_debug_lock(
|
||||||
|
|
||||||
|
|
||||||
@tractor.context
|
@tractor.context
|
||||||
async def _hijack_stdin_for_child(
|
async def lock_tty_for_child(
|
||||||
|
|
||||||
ctx: tractor.Context,
|
ctx: tractor.Context,
|
||||||
subactor_uid: Tuple[str, str]
|
subactor_uid: Tuple[str, str]
|
||||||
|
|
||||||
) -> str:
|
) -> str:
|
||||||
'''
|
'''
|
||||||
Hijack the tty in the root process of an actor tree such that
|
Lock the TTY in the root process of an actor tree in a new
|
||||||
the pdbpp debugger console can be allocated to a sub-actor for repl
|
inter-actor-context-task such that the ``pdbpp`` debugger console
|
||||||
bossing.
|
can be mutex-allocated to the calling sub-actor for REPL control
|
||||||
|
without interference by other processes / threads.
|
||||||
|
|
||||||
NOTE: this task is invoked in the root actor-process of the actor
|
NOTE: this task must be invoked in the root process of the actor
|
||||||
tree. It is meant to be invoked as an rpc-task which should be
|
tree. It is meant to be invoked as an rpc-task and should be
|
||||||
highly reliable at cleaning out the tty-lock state when complete!
|
highly reliable at releasing the mutex complete!
|
||||||
|
|
||||||
'''
|
'''
|
||||||
task_name = trio.lowlevel.current_task().name
|
task_name = trio.lowlevel.current_task().name
|
||||||
|
@ -288,7 +282,7 @@ async def _hijack_stdin_for_child(
|
||||||
with (
|
with (
|
||||||
trio.CancelScope(shield=True),
|
trio.CancelScope(shield=True),
|
||||||
):
|
):
|
||||||
async with _acquire_debug_lock(subactor_uid): # as lock:
|
async with _acquire_debug_lock_from_root_task(subactor_uid):
|
||||||
|
|
||||||
# indicate to child that we've locked stdio
|
# indicate to child that we've locked stdio
|
||||||
await ctx.started('Locked')
|
await ctx.started('Locked')
|
||||||
|
@ -311,14 +305,17 @@ async def wait_for_parent_stdin_hijack(
|
||||||
task_status: TaskStatus[trio.CancelScope] = trio.TASK_STATUS_IGNORED
|
task_status: TaskStatus[trio.CancelScope] = trio.TASK_STATUS_IGNORED
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Connect to the root actor via a ctx and invoke a task which locks
|
Connect to the root actor via a ``Context`` and invoke a task which
|
||||||
a root-local TTY lock.
|
locks a root-local TTY lock: ``lock_tty_for_child()``; this func
|
||||||
|
should be called in a new task from a child actor **and never the
|
||||||
|
root*.
|
||||||
|
|
||||||
This function is used by any sub-actor to acquire mutex access to
|
This function is used by any sub-actor to acquire mutex access to
|
||||||
pdb and the root's TTY for interactive debugging (see below inside
|
the ``pdb`` REPL and thus the root's TTY for interactive debugging
|
||||||
``_breakpoint()``). It can be used to ensure that an intermediate
|
(see below inside ``_breakpoint()``). It can be used to ensure that
|
||||||
nursery-owning actor does not clobber its children if they are in
|
an intermediate nursery-owning actor does not clobber its children
|
||||||
debug (see below inside ``maybe_wait_for_debugger()``).
|
if they are in debug (see below inside
|
||||||
|
``maybe_wait_for_debugger()``).
|
||||||
|
|
||||||
'''
|
'''
|
||||||
with trio.CancelScope(shield=True) as cs:
|
with trio.CancelScope(shield=True) as cs:
|
||||||
|
@ -330,7 +327,7 @@ async def wait_for_parent_stdin_hijack(
|
||||||
# this syncs to child's ``Context.started()`` call.
|
# this syncs to child's ``Context.started()`` call.
|
||||||
async with portal.open_context(
|
async with portal.open_context(
|
||||||
|
|
||||||
tractor._debug._hijack_stdin_for_child,
|
tractor._debug.lock_tty_for_child,
|
||||||
subactor_uid=actor_uid,
|
subactor_uid=actor_uid,
|
||||||
|
|
||||||
) as (ctx, val):
|
) as (ctx, val):
|
||||||
|
@ -390,8 +387,8 @@ async def _breakpoint(
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
breakpoint entry for engaging pdb machinery in the root or
|
Breakpoint entry for engaging debugger instance sync-interaction,
|
||||||
a subactor.
|
from async code, executing in actor runtime (task).
|
||||||
|
|
||||||
'''
|
'''
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
|
@ -411,12 +408,17 @@ async def _breakpoint(
|
||||||
Lock.local_pdb_complete = trio.Event()
|
Lock.local_pdb_complete = trio.Event()
|
||||||
|
|
||||||
# TODO: need a more robust check for the "root" actor
|
# TODO: need a more robust check for the "root" actor
|
||||||
if actor._parent_chan and not is_root_process():
|
if (
|
||||||
|
not is_root_process()
|
||||||
|
and actor._parent_chan # a connected child
|
||||||
|
):
|
||||||
|
|
||||||
if Lock.local_task_in_debug:
|
if Lock.local_task_in_debug:
|
||||||
|
|
||||||
|
# Recurrence entry case: this task already has the lock and
|
||||||
|
# is likely recurrently entering a breakpoint
|
||||||
if Lock.local_task_in_debug == task_name:
|
if Lock.local_task_in_debug == task_name:
|
||||||
# this task already has the lock and is
|
# noop on recurrent entry case
|
||||||
# likely recurrently entering a breakpoint
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# if **this** actor is already in debug mode block here
|
# if **this** actor is already in debug mode block here
|
||||||
|
@ -431,20 +433,6 @@ async def _breakpoint(
|
||||||
# entries/requests to the root process
|
# entries/requests to the root process
|
||||||
Lock.local_task_in_debug = task_name
|
Lock.local_task_in_debug = task_name
|
||||||
|
|
||||||
def child_release():
|
|
||||||
try:
|
|
||||||
# sometimes the ``trio`` might already be termianated in
|
|
||||||
# which case this call will raise.
|
|
||||||
Lock.local_pdb_complete.set()
|
|
||||||
finally:
|
|
||||||
# restore original sigint handler
|
|
||||||
undo_sigint()
|
|
||||||
# should always be cleared in the hijack hook aboved right?
|
|
||||||
# _local_task_in_debug = None
|
|
||||||
|
|
||||||
# assign unlock callback for debugger teardown hooks
|
|
||||||
Lock.pdb_release_hook = child_release
|
|
||||||
|
|
||||||
# this **must** be awaited by the caller and is done using the
|
# this **must** be awaited by the caller and is done using the
|
||||||
# root nursery so that the debugger can continue to run without
|
# root nursery so that the debugger can continue to run without
|
||||||
# being restricted by the scope of a new task nursery.
|
# being restricted by the scope of a new task nursery.
|
||||||
|
@ -460,7 +448,7 @@ async def _breakpoint(
|
||||||
actor.uid,
|
actor.uid,
|
||||||
)
|
)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
Lock.pdb_release_hook()
|
Lock.release()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
elif is_root_process():
|
elif is_root_process():
|
||||||
|
@ -468,8 +456,8 @@ async def _breakpoint(
|
||||||
# we also wait in the root-parent for any child that
|
# we also wait in the root-parent for any child that
|
||||||
# may have the tty locked prior
|
# may have the tty locked prior
|
||||||
# TODO: wait, what about multiple root tasks acquiring it though?
|
# TODO: wait, what about multiple root tasks acquiring it though?
|
||||||
# root process (us) already has it; ignore
|
|
||||||
if Lock.global_actor_in_debug == actor.uid:
|
if Lock.global_actor_in_debug == actor.uid:
|
||||||
|
# re-entrant root process already has it: noop.
|
||||||
return
|
return
|
||||||
|
|
||||||
# XXX: since we need to enter pdb synchronously below,
|
# XXX: since we need to enter pdb synchronously below,
|
||||||
|
@ -491,9 +479,6 @@ async def _breakpoint(
|
||||||
Lock.global_actor_in_debug = actor.uid
|
Lock.global_actor_in_debug = actor.uid
|
||||||
Lock.local_task_in_debug = task_name
|
Lock.local_task_in_debug = task_name
|
||||||
|
|
||||||
# the lock must be released on pdb completion
|
|
||||||
Lock.pdb_release_hook = Lock.root_release
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 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.
|
||||||
|
@ -501,7 +486,7 @@ async def _breakpoint(
|
||||||
debug_func(actor, pdb)
|
debug_func(actor, pdb)
|
||||||
|
|
||||||
except bdb.BdbQuit:
|
except bdb.BdbQuit:
|
||||||
Lock.maybe_release()
|
Lock.release()
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# XXX: apparently we can't do this without showing this frame
|
# XXX: apparently we can't do this without showing this frame
|
||||||
|
@ -597,9 +582,8 @@ def shield_sigint(
|
||||||
)
|
)
|
||||||
|
|
||||||
# child actor that has locked the debugger
|
# child actor that has locked the debugger
|
||||||
elif (
|
elif not is_root_process():
|
||||||
not is_root_process()
|
|
||||||
):
|
|
||||||
chan: Channel = actor._parent_chan
|
chan: Channel = actor._parent_chan
|
||||||
if not chan or not chan.connected():
|
if not chan or not chan.connected():
|
||||||
log.warning(
|
log.warning(
|
||||||
|
@ -738,7 +722,7 @@ async def _maybe_enter_pm(err):
|
||||||
):
|
):
|
||||||
log.debug("Actor crashed, entering debug mode")
|
log.debug("Actor crashed, entering debug mode")
|
||||||
await post_mortem()
|
await post_mortem()
|
||||||
Lock.maybe_release()
|
Lock.release()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -754,7 +738,7 @@ async def acquire_debug_lock(
|
||||||
|
|
||||||
This helper is for actor's who don't actually need
|
This helper is for actor's who don't actually need
|
||||||
to acquired the debugger but want to wait until the
|
to acquired the debugger but want to wait until the
|
||||||
lock is free in the tree root.
|
lock is free in the process-tree root.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if not debug_mode():
|
if not debug_mode():
|
||||||
|
|
Loading…
Reference in New Issue