forked from goodboy/tractor
Fix `.devx.maybe_wait_for_debugger()` polling deats
When entered by the root actor avoid excessive polling cycles by, - blocking on the `Lock.no_remote_has_tty: trio.Event` and breaking *immediately* when set (though we should really also lock it from the root right?) to avoid extra loops.. - shielding the `await trio.sleep(poll_delay)` call to avoid any local cancellation causing the (presumably root-actor task) caller to move on (possibly to cancel its children) and instead to continue poll-blocking until the lock is actually released by its user. - `break` the poll loop immediately if no remote locker is detected. - use `.pdb()` level for reporting lock state changes. Also add a #TODO to handle calls by non-root actors as it pertains tomodden_spawn_from_client_req
parent
114ec36436
commit
df50d78042
|
@ -1,18 +1,19 @@
|
||||||
# tractor: structured concurrent "actors".
|
# tractor: structured concurrent "actors".
|
||||||
# Copyright 2018-eternity Tyler Goodlet.
|
# Copyright 2018-eternity Tyler Goodlet.
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# modify it under the terms of the GNU Affero General Public License
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
# as published by the Free Software Foundation, either version 3 of
|
||||||
# (at your option) any later version.
|
# the License, or (at your option) any later version.
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
# This program is distributed in the hope that it will be useful, but
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
# GNU Affero General Public License for more details.
|
# Affero General Public License for more details.
|
||||||
|
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# License along with this program. If not, see
|
||||||
|
# <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Multi-core debugging for da peeps!
|
Multi-core debugging for da peeps!
|
||||||
|
@ -43,6 +44,7 @@ from types import FrameType
|
||||||
import pdbp
|
import pdbp
|
||||||
import tractor
|
import tractor
|
||||||
import trio
|
import trio
|
||||||
|
from trio.lowlevel import current_task
|
||||||
from trio_typing import (
|
from trio_typing import (
|
||||||
TaskStatus,
|
TaskStatus,
|
||||||
# Task,
|
# Task,
|
||||||
|
@ -50,6 +52,7 @@ from trio_typing import (
|
||||||
|
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from .._state import (
|
from .._state import (
|
||||||
|
current_actor,
|
||||||
is_root_process,
|
is_root_process,
|
||||||
debug_mode,
|
debug_mode,
|
||||||
)
|
)
|
||||||
|
@ -238,7 +241,7 @@ async def _acquire_debug_lock_from_root_task(
|
||||||
to the ``pdb`` repl.
|
to the ``pdb`` repl.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
task_name: str = trio.lowlevel.current_task().name
|
task_name: str = current_task().name
|
||||||
we_acquired: bool = False
|
we_acquired: bool = False
|
||||||
|
|
||||||
log.runtime(
|
log.runtime(
|
||||||
|
@ -323,8 +326,7 @@ async def lock_tty_for_child(
|
||||||
highly reliable at releasing the mutex complete!
|
highly reliable at releasing the mutex complete!
|
||||||
|
|
||||||
'''
|
'''
|
||||||
task_name = trio.lowlevel.current_task().name
|
task_name: str = current_task().name
|
||||||
|
|
||||||
if tuple(subactor_uid) in Lock._blocked:
|
if tuple(subactor_uid) in Lock._blocked:
|
||||||
log.warning(
|
log.warning(
|
||||||
f'Actor {subactor_uid} is blocked from acquiring debug lock\n'
|
f'Actor {subactor_uid} is blocked from acquiring debug lock\n'
|
||||||
|
@ -407,11 +409,13 @@ async def wait_for_parent_stdin_hijack(
|
||||||
assert val == 'Locked'
|
assert val == 'Locked'
|
||||||
|
|
||||||
async with ctx.open_stream() as stream:
|
async with ctx.open_stream() as stream:
|
||||||
# unblock local caller
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# unblock local caller
|
||||||
assert Lock.local_pdb_complete
|
assert Lock.local_pdb_complete
|
||||||
task_status.started(cs)
|
task_status.started(cs)
|
||||||
|
|
||||||
|
# wait for local task to exit and
|
||||||
|
# release the REPL
|
||||||
await Lock.local_pdb_complete.wait()
|
await Lock.local_pdb_complete.wait()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
@ -468,7 +472,7 @@ def shield_sigint_handler(
|
||||||
|
|
||||||
uid_in_debug: tuple[str, str] | None = Lock.global_actor_in_debug
|
uid_in_debug: tuple[str, str] | None = Lock.global_actor_in_debug
|
||||||
|
|
||||||
actor = tractor.current_actor()
|
actor = current_actor()
|
||||||
# print(f'{actor.uid} in HANDLER with ')
|
# print(f'{actor.uid} in HANDLER with ')
|
||||||
|
|
||||||
def do_cancel():
|
def do_cancel():
|
||||||
|
@ -613,7 +617,7 @@ def _set_trace(
|
||||||
shield: bool = False,
|
shield: bool = False,
|
||||||
):
|
):
|
||||||
__tracebackhide__: bool = True
|
__tracebackhide__: bool = True
|
||||||
actor: tractor.Actor = actor or tractor.current_actor()
|
actor: tractor.Actor = actor or current_actor()
|
||||||
|
|
||||||
# start 2 levels up in user code
|
# start 2 levels up in user code
|
||||||
frame: FrameType | None = sys._getframe()
|
frame: FrameType | None = sys._getframe()
|
||||||
|
@ -683,9 +687,9 @@ async def pause(
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# __tracebackhide__ = True
|
# __tracebackhide__ = True
|
||||||
actor = tractor.current_actor()
|
actor = current_actor()
|
||||||
pdb, undo_sigint = mk_mpdb()
|
pdb, undo_sigint = mk_mpdb()
|
||||||
task_name = trio.lowlevel.current_task().name
|
task_name: str = trio.lowlevel.current_task().name
|
||||||
|
|
||||||
if (
|
if (
|
||||||
not Lock.local_pdb_complete
|
not Lock.local_pdb_complete
|
||||||
|
@ -836,7 +840,7 @@ async def pause(
|
||||||
# runtime aware version which takes care of all .
|
# runtime aware version which takes care of all .
|
||||||
def pause_from_sync() -> None:
|
def pause_from_sync() -> None:
|
||||||
print("ENTER SYNC PAUSE")
|
print("ENTER SYNC PAUSE")
|
||||||
actor: tractor.Actor = tractor.current_actor(
|
actor: tractor.Actor = current_actor(
|
||||||
err_on_no_runtime=False,
|
err_on_no_runtime=False,
|
||||||
)
|
)
|
||||||
if actor:
|
if actor:
|
||||||
|
@ -971,9 +975,10 @@ async def acquire_debug_lock(
|
||||||
'''
|
'''
|
||||||
Grab root's debug lock on entry, release on exit.
|
Grab root's debug lock on entry, release on exit.
|
||||||
|
|
||||||
This helper is for actor's who don't actually need
|
This helper is for actor's who don't actually need to acquired
|
||||||
to acquired the debugger but want to wait until the
|
the debugger but want to wait until the lock is free in the
|
||||||
lock is free in the process-tree root.
|
process-tree root such that they don't clobber an ongoing pdb
|
||||||
|
REPL session in some peer or child!
|
||||||
|
|
||||||
'''
|
'''
|
||||||
if not debug_mode():
|
if not debug_mode():
|
||||||
|
@ -1013,43 +1018,71 @@ async def maybe_wait_for_debugger(
|
||||||
# tearing down.
|
# tearing down.
|
||||||
sub_in_debug: tuple[str, str] | None = None
|
sub_in_debug: tuple[str, str] | None = None
|
||||||
|
|
||||||
for _ in range(poll_steps):
|
for istep in range(poll_steps):
|
||||||
|
|
||||||
if Lock.global_actor_in_debug:
|
if sub_in_debug := Lock.global_actor_in_debug:
|
||||||
sub_in_debug = tuple(Lock.global_actor_in_debug)
|
log.pdb(
|
||||||
|
f'Lock in use by {sub_in_debug}'
|
||||||
log.debug('Root polling for debug')
|
)
|
||||||
|
# TODO: could this make things more deterministic?
|
||||||
with trio.CancelScope(shield=True):
|
# wait to see if a sub-actor task will be
|
||||||
await trio.sleep(poll_delay)
|
# scheduled and grab the tty lock on the next
|
||||||
|
# tick?
|
||||||
# TODO: could this make things more deterministic? wait
|
# XXX => but it doesn't seem to work..
|
||||||
# to see if a sub-actor task will be scheduled and grab
|
|
||||||
# the tty lock on the next tick?
|
|
||||||
# XXX: doesn't seem to work
|
|
||||||
# await trio.testing.wait_all_tasks_blocked(cushion=0)
|
# await trio.testing.wait_all_tasks_blocked(cushion=0)
|
||||||
|
|
||||||
debug_complete = Lock.no_remote_has_tty
|
debug_complete: trio.Event|None = Lock.no_remote_has_tty
|
||||||
if (
|
if (
|
||||||
debug_complete
|
debug_complete
|
||||||
and sub_in_debug is not None
|
|
||||||
and not debug_complete.is_set()
|
and not debug_complete.is_set()
|
||||||
|
and sub_in_debug is not None
|
||||||
):
|
):
|
||||||
log.pdb(
|
log.pdb(
|
||||||
'Root has errored but pdb is in use by '
|
'Root has errored but pdb is in use by child\n'
|
||||||
f'child {sub_in_debug}\n'
|
'Waiting on tty lock to release..\n'
|
||||||
'Waiting on tty lock to release..'
|
f'uid: {sub_in_debug}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
await debug_complete.wait()
|
await debug_complete.wait()
|
||||||
|
log.pdb(
|
||||||
|
f'Child subactor released debug lock!\n'
|
||||||
|
f'uid: {sub_in_debug}\n'
|
||||||
|
)
|
||||||
|
if debug_complete.is_set():
|
||||||
|
break
|
||||||
|
|
||||||
|
# is no subactor locking debugger currently?
|
||||||
|
elif (
|
||||||
|
debug_complete is None
|
||||||
|
or sub_in_debug is None
|
||||||
|
):
|
||||||
|
log.pdb(
|
||||||
|
'Root acquired debug TTY LOCK from child\n'
|
||||||
|
f'uid: {sub_in_debug}'
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
# TODO: don't need this right?
|
||||||
|
# await trio.lowlevel.checkpoint()
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
'Root polling for debug:\n'
|
||||||
|
f'poll step: {istep}\n'
|
||||||
|
f'poll delya: {poll_delay}'
|
||||||
|
)
|
||||||
|
with trio.CancelScope(shield=True):
|
||||||
await trio.sleep(poll_delay)
|
await trio.sleep(poll_delay)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
log.debug(
|
log.pdb('Root acquired debug TTY LOCK')
|
||||||
'Root acquired TTY LOCK'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# # TODO: non-root call for #320?
|
||||||
|
# this_uid: tuple[str, str] = current_actor().uid
|
||||||
|
# async with acquire_debug_lock(
|
||||||
|
# subactor_uid=this_uid,
|
||||||
|
# ):
|
||||||
|
# pass
|
||||||
|
|
||||||
# TODO: better naming and what additionals?
|
# TODO: better naming and what additionals?
|
||||||
# - [ ] optional runtime plugging?
|
# - [ ] optional runtime plugging?
|
||||||
|
|
Loading…
Reference in New Issue