More `.pause_from_sync()` in bg-threads "polish"

Various `try`/`except` blocks around external APIs that raise when not
running inside an `tractor` and/or some async framework (mostly to avoid
too-late/benign error tbs on certain classes of actor tree teardown):
- for the `log.pdb()` prompts emitted before REPL console entry.
- inside `DebugStatus.is_main_trio_thread()`'s call to `sniffio`.
- in `_post_mortem()` by catching `NoRuntime` when called from a thread
  still active after the `.open_root_actor()` has already exited.

Also,
- create a dedicated `DebugStateError` for raising instead of `assert`s
  when we have actual debug-request inconsistencies (as seem to be most
  likely with bg thread usage of `breakpoint()`).
- show the `open_crash_handler()` frame on `bdb.BdbQuit` (for now?)
multihost_exs
Tyler Goodlet 2024-08-22 17:10:01 -04:00
parent ae95e0c83e
commit f7f738638d
1 changed files with 71 additions and 19 deletions

View File

@ -72,6 +72,10 @@ from tractor.to_asyncio import run_trio_task_in_future
from tractor.log import get_logger from tractor.log import get_logger
from tractor._context import Context from tractor._context import Context
from tractor import _state from tractor import _state
from tractor._exceptions import (
InternalError,
NoRuntime,
)
from tractor._state import ( from tractor._state import (
current_actor, current_actor,
is_root_process, is_root_process,
@ -691,6 +695,14 @@ async def lock_stdio_for_peer(
DebugStatus.unshield_sigint() DebugStatus.unshield_sigint()
class DebugStateError(InternalError):
'''
Something inconsistent or unexpected happend with a sub-actor's
debug mutex request to the root actor.
'''
# TODO: rename to ReplState or somethin? # TODO: rename to ReplState or somethin?
# DebugRequest, make it a singleton instance? # DebugRequest, make it a singleton instance?
class DebugStatus: class DebugStatus:
@ -860,20 +872,37 @@ class DebugStatus:
`trio.to_thread.run_sync()`. `trio.to_thread.run_sync()`.
''' '''
try:
async_lib: str = sniffio.current_async_library()
except sniffio.AsyncLibraryNotFoundError:
async_lib = None
is_main_thread: bool = trio._util.is_main_thread()
# ^TODO, since this is private, @oremanj says
# we should just copy the impl for now..?
if is_main_thread:
thread_name: str = 'main'
else:
thread_name: str = threading.current_thread().name
is_trio_main = ( is_trio_main = (
# TODO: since this is private, @oremanj says is_main_thread
# we should just copy the impl for now..
(is_main_thread := trio._util.is_main_thread())
and and
(async_lib := sniffio.current_async_library()) == 'trio' (async_lib == 'trio')
) )
if (
not is_trio_main report: str = f'Running thread: {thread_name!r}\n'
and is_main_thread if async_lib:
): report += (
log.warning(
f'Current async-lib detected by `sniffio`: {async_lib}\n' f'Current async-lib detected by `sniffio`: {async_lib}\n'
) )
else:
report += (
'No async-lib detected (by `sniffio`) ??\n'
)
if not is_trio_main:
log.warning(report)
return is_trio_main return is_trio_main
# XXX apparently unreliable..see ^ # XXX apparently unreliable..see ^
# ( # (
@ -2615,7 +2644,15 @@ def pause_from_sync(
bg_task: Task = current_task() bg_task: Task = current_task()
# assert repl is repl # assert repl is repl
assert bg_task is repl_owner # assert bg_task is repl_owner
if bg_task is not repl_owner:
raise DebugStateError(
f'The registered bg task for this debug request is NOT its owner ??\n'
f'bg_task: {bg_task}\n'
f'repl_owner: {repl_owner}\n\n'
f'{DebugStatus.repr()}\n'
)
# NOTE: normally set inside `_enter_repl_sync()` # NOTE: normally set inside `_enter_repl_sync()`
DebugStatus.repl_task: str = repl_owner DebugStatus.repl_task: str = repl_owner
@ -2715,17 +2752,28 @@ def _post_mortem(
''' '''
__tracebackhide__: bool = hide_tb __tracebackhide__: bool = hide_tb
actor: tractor.Actor = current_actor() try:
actor: tractor.Actor = current_actor()
actor_repr: str = str(actor.uid)
# ^TODO, instead a nice runtime-info + maddr + uid?
# -[ ] impl a `Actor.__repr()__`??
# |_ <task>:<thread> @ <actor>
except NoRuntime:
actor_repr: str = '<no-actor-runtime?>'
try:
task_repr: Task = current_task()
except RuntimeError:
task_repr: str = '<unknown-Task>'
# TODO: print the actor supervion tree up to the root # TODO: print the actor supervion tree up to the root
# here! Bo # here! Bo
log.pdb( log.pdb(
f'{_crash_msg}\n' f'{_crash_msg}\n'
f'x>(\n' f'x>(\n'
f' |_ {current_task()} @ {actor.uid}\n' f' |_ {task_repr} @ {actor_repr}\n'
# TODO: make an `Actor.__repr()__`
# f'|_ {current_task()} @ {actor.name}\n'
) )
# NOTE only replacing this from `pdbp.xpm()` to add the # NOTE only replacing this from `pdbp.xpm()` to add the
@ -3022,11 +3070,15 @@ def open_crash_handler(
if type(err) not in ignore: if type(err) not in ignore:
# use our re-impl-ed version # use our re-impl-ed version
_post_mortem( try:
repl=mk_pdb(), _post_mortem(
tb=sys.exc_info()[2], repl=mk_pdb(),
api_frame=inspect.currentframe().f_back, tb=sys.exc_info()[2],
) api_frame=inspect.currentframe().f_back,
)
except bdb.BdbQuit:
__tracebackhide__: bool = False
raise
# XXX NOTE, `pdbp`'s version seems to lose the up-stack # XXX NOTE, `pdbp`'s version seems to lose the up-stack
# tb-info? # tb-info?