Show runtime nursery frames on internal errors
Much like other recent changes attempt to detect runtime-bug-causing crashes and only show the runtime-endpoint frame when present. Adds a `ActorNursery._scope_error: BaseException|None` attr to aid with detection. Also toss in some todo notes for removing and replacing the `.run_in_actor()` method API.runtime_to_msgspec
parent
4ef77bb64f
commit
fde62c72be
|
@ -84,6 +84,7 @@ class ActorNursery:
|
||||||
ria_nursery: trio.Nursery,
|
ria_nursery: trio.Nursery,
|
||||||
da_nursery: trio.Nursery,
|
da_nursery: trio.Nursery,
|
||||||
errors: dict[tuple[str, str], BaseException],
|
errors: dict[tuple[str, str], BaseException],
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
# self.supervisor = supervisor # TODO
|
# self.supervisor = supervisor # TODO
|
||||||
self._actor: Actor = actor
|
self._actor: Actor = actor
|
||||||
|
@ -105,6 +106,7 @@ class ActorNursery:
|
||||||
self._at_least_one_child_in_debug: bool = False
|
self._at_least_one_child_in_debug: bool = False
|
||||||
self.errors = errors
|
self.errors = errors
|
||||||
self.exited = trio.Event()
|
self.exited = trio.Event()
|
||||||
|
self._scope_error: BaseException|None = None
|
||||||
|
|
||||||
# NOTE: when no explicit call is made to
|
# NOTE: when no explicit call is made to
|
||||||
# `.open_root_actor()` by application code,
|
# `.open_root_actor()` by application code,
|
||||||
|
@ -117,7 +119,9 @@ class ActorNursery:
|
||||||
async def start_actor(
|
async def start_actor(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
|
|
||||||
*,
|
*,
|
||||||
|
|
||||||
bind_addrs: list[tuple[str, int]] = [_default_bind_addr],
|
bind_addrs: list[tuple[str, int]] = [_default_bind_addr],
|
||||||
rpc_module_paths: list[str]|None = None,
|
rpc_module_paths: list[str]|None = None,
|
||||||
enable_modules: list[str]|None = None,
|
enable_modules: list[str]|None = None,
|
||||||
|
@ -125,6 +129,7 @@ class ActorNursery:
|
||||||
nursery: trio.Nursery|None = None,
|
nursery: trio.Nursery|None = None,
|
||||||
debug_mode: bool|None = None,
|
debug_mode: bool|None = None,
|
||||||
infect_asyncio: bool = False,
|
infect_asyncio: bool = False,
|
||||||
|
|
||||||
) -> Portal:
|
) -> Portal:
|
||||||
'''
|
'''
|
||||||
Start a (daemon) actor: an process that has no designated
|
Start a (daemon) actor: an process that has no designated
|
||||||
|
@ -189,6 +194,13 @@ class ActorNursery:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: DEPRECATE THIS:
|
||||||
|
# -[ ] impl instead as a hilevel wrapper on
|
||||||
|
# top of a `@context` style invocation.
|
||||||
|
# |_ dynamic @context decoration on child side
|
||||||
|
# |_ implicit `Portal.open_context() as (ctx, first):`
|
||||||
|
# and `return first` on parent side.
|
||||||
|
# -[ ] use @api_frame on the wrapper
|
||||||
async def run_in_actor(
|
async def run_in_actor(
|
||||||
self,
|
self,
|
||||||
|
|
||||||
|
@ -221,7 +233,7 @@ class ActorNursery:
|
||||||
# use the explicit function name if not provided
|
# use the explicit function name if not provided
|
||||||
name = fn.__name__
|
name = fn.__name__
|
||||||
|
|
||||||
portal = await self.start_actor(
|
portal: Portal = await self.start_actor(
|
||||||
name,
|
name,
|
||||||
enable_modules=[mod_path] + (
|
enable_modules=[mod_path] + (
|
||||||
enable_modules or rpc_module_paths or []
|
enable_modules or rpc_module_paths or []
|
||||||
|
@ -250,6 +262,7 @@ class ActorNursery:
|
||||||
)
|
)
|
||||||
return portal
|
return portal
|
||||||
|
|
||||||
|
# @api_frame
|
||||||
async def cancel(
|
async def cancel(
|
||||||
self,
|
self,
|
||||||
hard_kill: bool = False,
|
hard_kill: bool = False,
|
||||||
|
@ -346,7 +359,12 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
||||||
actor: Actor,
|
actor: Actor,
|
||||||
|
|
||||||
) -> typing.AsyncGenerator[ActorNursery, None]:
|
) -> typing.AsyncGenerator[ActorNursery, None]:
|
||||||
__tracebackhide__ = True
|
|
||||||
|
# normally don't need to show user by default
|
||||||
|
__tracebackhide__: bool = True
|
||||||
|
|
||||||
|
outer_err: BaseException|None = None
|
||||||
|
inner_err: BaseException|None = None
|
||||||
|
|
||||||
# the collection of errors retreived from spawned sub-actors
|
# the collection of errors retreived from spawned sub-actors
|
||||||
errors: dict[tuple[str, str], BaseException] = {}
|
errors: dict[tuple[str, str], BaseException] = {}
|
||||||
|
@ -356,7 +374,7 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
||||||
# handling errors that are generated by the inner nursery in
|
# handling errors that are generated by the inner nursery in
|
||||||
# a supervisor strategy **before** blocking indefinitely to wait for
|
# a supervisor strategy **before** blocking indefinitely to wait for
|
||||||
# actors spawned in "daemon mode" (aka started using
|
# actors spawned in "daemon mode" (aka started using
|
||||||
# ``ActorNursery.start_actor()``).
|
# `ActorNursery.start_actor()`).
|
||||||
|
|
||||||
# errors from this daemon actor nursery bubble up to caller
|
# errors from this daemon actor nursery bubble up to caller
|
||||||
async with trio.open_nursery() as da_nursery:
|
async with trio.open_nursery() as da_nursery:
|
||||||
|
@ -391,7 +409,8 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
||||||
)
|
)
|
||||||
an._join_procs.set()
|
an._join_procs.set()
|
||||||
|
|
||||||
except BaseException as inner_err:
|
except BaseException as _inner_err:
|
||||||
|
inner_err = _inner_err
|
||||||
errors[actor.uid] = inner_err
|
errors[actor.uid] = inner_err
|
||||||
|
|
||||||
# If we error in the root but the debugger is
|
# If we error in the root but the debugger is
|
||||||
|
@ -469,8 +488,10 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
||||||
Exception,
|
Exception,
|
||||||
BaseExceptionGroup,
|
BaseExceptionGroup,
|
||||||
trio.Cancelled
|
trio.Cancelled
|
||||||
|
) as _outer_err:
|
||||||
|
outer_err = _outer_err
|
||||||
|
|
||||||
) as err:
|
an._scope_error = outer_err or inner_err
|
||||||
|
|
||||||
# XXX: yet another guard before allowing the cancel
|
# XXX: yet another guard before allowing the cancel
|
||||||
# sequence in case a (single) child is in debug.
|
# sequence in case a (single) child is in debug.
|
||||||
|
@ -485,7 +506,7 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
||||||
if an._children:
|
if an._children:
|
||||||
log.cancel(
|
log.cancel(
|
||||||
'Actor-nursery cancelling due error type:\n'
|
'Actor-nursery cancelling due error type:\n'
|
||||||
f'{err}\n'
|
f'{outer_err}\n'
|
||||||
)
|
)
|
||||||
with trio.CancelScope(shield=True):
|
with trio.CancelScope(shield=True):
|
||||||
await an.cancel()
|
await an.cancel()
|
||||||
|
@ -512,6 +533,13 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
||||||
else:
|
else:
|
||||||
raise list(errors.values())[0]
|
raise list(errors.values())[0]
|
||||||
|
|
||||||
|
# show frame on any (likely) internal error
|
||||||
|
if (
|
||||||
|
not an.cancelled
|
||||||
|
and an._scope_error
|
||||||
|
):
|
||||||
|
__tracebackhide__: bool = False
|
||||||
|
|
||||||
# da_nursery scope end - nursery checkpoint
|
# da_nursery scope end - nursery checkpoint
|
||||||
# final exit
|
# final exit
|
||||||
|
|
||||||
|
@ -537,7 +565,7 @@ async def open_nursery(
|
||||||
which cancellation scopes correspond to each spawned subactor set.
|
which cancellation scopes correspond to each spawned subactor set.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
__tracebackhide__ = True
|
__tracebackhide__: bool = True
|
||||||
implicit_runtime: bool = False
|
implicit_runtime: bool = False
|
||||||
actor: Actor = current_actor(err_on_no_runtime=False)
|
actor: Actor = current_actor(err_on_no_runtime=False)
|
||||||
an: ActorNursery|None = None
|
an: ActorNursery|None = None
|
||||||
|
@ -588,6 +616,14 @@ async def open_nursery(
|
||||||
an.exited.set()
|
an.exited.set()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
# show frame on any internal runtime-scope error
|
||||||
|
if (
|
||||||
|
an
|
||||||
|
and not an.cancelled
|
||||||
|
and an._scope_error
|
||||||
|
):
|
||||||
|
__tracebackhide__: bool = False
|
||||||
|
|
||||||
msg: str = (
|
msg: str = (
|
||||||
'Actor-nursery exited\n'
|
'Actor-nursery exited\n'
|
||||||
f'|_{an}\n'
|
f'|_{an}\n'
|
||||||
|
|
Loading…
Reference in New Issue