from collections.abc import Awaitable, Callable import tractor import trio async def breakpoint_forever() -> None: ''' Indefinitely re-enter debugger in child actor. ''' while True: await trio.sleep(0.1) await tractor.pause() async def name_error() -> None: ''' Raise a ``NameError``. ''' getattr(doggypants) # noqa async def spawn_error() -> None: ''' A nested nursery that triggers another ``NameError``. ''' async with tractor.open_nursery() as an: return await tractor.to_actor.run( name_error, an=an, name='name_error_1', ) async def main() -> None: ''' The main ``tractor`` routine. The process tree should look as approximately as follows: -python examples/debugging/multi_subactors.py |-python -m tractor._child --uid ('name_error', 'a7caf490 ...) |-python -m tractor._child --uid ('bp_forever', '1f787a7e ...) `-python -m tractor._child --uid ('spawn_error', '52ee14a5 ...) `-python -m tractor._child --uid ('name_error', '3391222c ...) ''' errors: list[BaseException] = [] async with tractor.open_nursery( debug_mode=True, # loglevel='runtime', ) as an: async def run_and_collect( fn: Callable[[], Awaitable[object]], ) -> None: ''' One-shot whose (boxed) error is stashed instead of raised so a sibling's crash never cancels the others before they've had their own debugger sessions (the 'collect all errors' the legacy `run_in_actor()` API did implicitly at nursery teardown). ''' try: await tractor.to_actor.run(fn, an=an) except tractor.RemoteActorError as rae: errors.append(rae) # Spawn all one-shot task actors, collecting (vs. # raising) their errors. async with trio.open_nursery() as tn: tn.start_soon(run_and_collect, breakpoint_forever) tn.start_soon(run_and_collect, name_error) tn.start_soon(run_and_collect, spawn_error) if errors: raise BaseExceptionGroup( 'multi_subactors errored!', errors, ) if __name__ == '__main__': trio.run(main)