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,
|
||||
da_nursery: trio.Nursery,
|
||||
errors: dict[tuple[str, str], BaseException],
|
||||
|
||||
) -> None:
|
||||
# self.supervisor = supervisor # TODO
|
||||
self._actor: Actor = actor
|
||||
|
@ -105,6 +106,7 @@ class ActorNursery:
|
|||
self._at_least_one_child_in_debug: bool = False
|
||||
self.errors = errors
|
||||
self.exited = trio.Event()
|
||||
self._scope_error: BaseException|None = None
|
||||
|
||||
# NOTE: when no explicit call is made to
|
||||
# `.open_root_actor()` by application code,
|
||||
|
@ -117,7 +119,9 @@ class ActorNursery:
|
|||
async def start_actor(
|
||||
self,
|
||||
name: str,
|
||||
|
||||
*,
|
||||
|
||||
bind_addrs: list[tuple[str, int]] = [_default_bind_addr],
|
||||
rpc_module_paths: list[str]|None = None,
|
||||
enable_modules: list[str]|None = None,
|
||||
|
@ -125,6 +129,7 @@ class ActorNursery:
|
|||
nursery: trio.Nursery|None = None,
|
||||
debug_mode: bool|None = None,
|
||||
infect_asyncio: bool = False,
|
||||
|
||||
) -> Portal:
|
||||
'''
|
||||
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(
|
||||
self,
|
||||
|
||||
|
@ -221,7 +233,7 @@ class ActorNursery:
|
|||
# use the explicit function name if not provided
|
||||
name = fn.__name__
|
||||
|
||||
portal = await self.start_actor(
|
||||
portal: Portal = await self.start_actor(
|
||||
name,
|
||||
enable_modules=[mod_path] + (
|
||||
enable_modules or rpc_module_paths or []
|
||||
|
@ -250,6 +262,7 @@ class ActorNursery:
|
|||
)
|
||||
return portal
|
||||
|
||||
# @api_frame
|
||||
async def cancel(
|
||||
self,
|
||||
hard_kill: bool = False,
|
||||
|
@ -346,7 +359,12 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
|||
actor: Actor,
|
||||
|
||||
) -> 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
|
||||
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
|
||||
# a supervisor strategy **before** blocking indefinitely to wait for
|
||||
# actors spawned in "daemon mode" (aka started using
|
||||
# ``ActorNursery.start_actor()``).
|
||||
# `ActorNursery.start_actor()`).
|
||||
|
||||
# errors from this daemon actor nursery bubble up to caller
|
||||
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()
|
||||
|
||||
except BaseException as inner_err:
|
||||
except BaseException as _inner_err:
|
||||
inner_err = _inner_err
|
||||
errors[actor.uid] = inner_err
|
||||
|
||||
# If we error in the root but the debugger is
|
||||
|
@ -469,8 +488,10 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
|||
Exception,
|
||||
BaseExceptionGroup,
|
||||
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
|
||||
# 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:
|
||||
log.cancel(
|
||||
'Actor-nursery cancelling due error type:\n'
|
||||
f'{err}\n'
|
||||
f'{outer_err}\n'
|
||||
)
|
||||
with trio.CancelScope(shield=True):
|
||||
await an.cancel()
|
||||
|
@ -512,6 +533,13 @@ async def _open_and_supervise_one_cancels_all_nursery(
|
|||
else:
|
||||
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
|
||||
# final exit
|
||||
|
||||
|
@ -537,7 +565,7 @@ async def open_nursery(
|
|||
which cancellation scopes correspond to each spawned subactor set.
|
||||
|
||||
'''
|
||||
__tracebackhide__ = True
|
||||
__tracebackhide__: bool = True
|
||||
implicit_runtime: bool = False
|
||||
actor: Actor = current_actor(err_on_no_runtime=False)
|
||||
an: ActorNursery|None = None
|
||||
|
@ -588,6 +616,14 @@ async def open_nursery(
|
|||
an.exited.set()
|
||||
|
||||
finally:
|
||||
# show frame on any internal runtime-scope error
|
||||
if (
|
||||
an
|
||||
and not an.cancelled
|
||||
and an._scope_error
|
||||
):
|
||||
__tracebackhide__: bool = False
|
||||
|
||||
msg: str = (
|
||||
'Actor-nursery exited\n'
|
||||
f'|_{an}\n'
|
||||
|
|
Loading…
Reference in New Issue