Re-route errors from spawn tasks and mngr task to handler
							parent
							
								
									0488f5e57e
								
							
						
					
					
						commit
						8a59713d48
					
				| 
						 | 
					@ -34,14 +34,12 @@ class ActorNursery:
 | 
				
			||||||
    def __init__(
 | 
					    def __init__(
 | 
				
			||||||
        self,
 | 
					        self,
 | 
				
			||||||
        actor: Actor,
 | 
					        actor: Actor,
 | 
				
			||||||
        ria_nursery: trio.Nursery,
 | 
					        spawn_nursery: trio.Nursery,
 | 
				
			||||||
        da_nursery: trio.Nursery,
 | 
					 | 
				
			||||||
        errors: Dict[Tuple[str, str], Exception],
 | 
					        errors: Dict[Tuple[str, str], Exception],
 | 
				
			||||||
    ) -> None:
 | 
					    ) -> None:
 | 
				
			||||||
        # self.supervisor = supervisor  # TODO
 | 
					        # self.supervisor = supervisor  # TODO
 | 
				
			||||||
        self._actor: Actor = actor
 | 
					        self._actor: Actor = actor
 | 
				
			||||||
        self._ria_nursery = ria_nursery
 | 
					        self._spawn_n = spawn_nursery
 | 
				
			||||||
        self._da_nursery = da_nursery
 | 
					 | 
				
			||||||
        self._children: Dict[
 | 
					        self._children: Dict[
 | 
				
			||||||
            Tuple[str, str],
 | 
					            Tuple[str, str],
 | 
				
			||||||
            Tuple[Actor, mp.Process, Optional[Portal]]
 | 
					            Tuple[Actor, mp.Process, Optional[Portal]]
 | 
				
			||||||
| 
						 | 
					@ -99,7 +97,7 @@ class ActorNursery:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # start a task to spawn a process
 | 
					        # start a task to spawn a process
 | 
				
			||||||
        # blocks until process has been started and a portal setup
 | 
					        # blocks until process has been started and a portal setup
 | 
				
			||||||
        nursery = nursery or self._da_nursery
 | 
					        nursery = nursery or self._spawn_n
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # XXX: the type ignore is actually due to a `mypy` bug
 | 
					        # XXX: the type ignore is actually due to a `mypy` bug
 | 
				
			||||||
        return await nursery.start(  # type: ignore
 | 
					        return await nursery.start(  # type: ignore
 | 
				
			||||||
| 
						 | 
					@ -149,7 +147,7 @@ class ActorNursery:
 | 
				
			||||||
            bind_addr=bind_addr,
 | 
					            bind_addr=bind_addr,
 | 
				
			||||||
            loglevel=loglevel,
 | 
					            loglevel=loglevel,
 | 
				
			||||||
            # use the run_in_actor nursery
 | 
					            # use the run_in_actor nursery
 | 
				
			||||||
            nursery=self._ria_nursery,
 | 
					            nursery=self._spawn_n,
 | 
				
			||||||
            infect_asyncio=infect_asyncio,
 | 
					            infect_asyncio=infect_asyncio,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -182,77 +180,35 @@ class ActorNursery:
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        self.cancelled = True
 | 
					        self.cancelled = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        childs = tuple(self._children.keys())
 | 
					        # entries may be poppsed by the spawning backend as
 | 
				
			||||||
 | 
					        # actors cancel individually
 | 
				
			||||||
 | 
					        childs = self._children.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        log.cancel(
 | 
					        log.cancel(
 | 
				
			||||||
            f"Cancelling nursery in {self._actor.uid} with children\n{childs}"
 | 
					            f'Cancelling nursery in {self._actor.uid} with children\n'
 | 
				
			||||||
 | 
					            f'{childs.keys()}'
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # wake up all spawn tasks to move on as those nursery
 | 
				
			||||||
 | 
					        # has ``__aexit__()``-ed
 | 
				
			||||||
 | 
					        self._join_procs.set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await maybe_wait_for_debugger()
 | 
					        await maybe_wait_for_debugger()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # wake up all spawn tasks
 | 
					        # one-cancels-all strat
 | 
				
			||||||
        self._join_procs.set()
 | 
					        async with trio.open_nursery() as cancel_sender:
 | 
				
			||||||
 | 
					            for subactor, proc, portal in childs.values():
 | 
				
			||||||
 | 
					                cancel_sender.start_soon(portal.cancel_actor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # cancel all spawner nurseries
 | 
					        # cancel all spawner tasks
 | 
				
			||||||
        self._ria_nursery.cancel_scope.cancel()
 | 
					        # self._spawn_n.cancel_scope.cancel()
 | 
				
			||||||
        self._da_nursery.cancel_scope.cancel()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def _handle_err(
 | 
				
			||||||
 | 
					        self,
 | 
				
			||||||
 | 
					        err: BaseException,
 | 
				
			||||||
 | 
					        portal: Optional[Portal] = None,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@asynccontextmanager
 | 
					    ) -> None:
 | 
				
			||||||
async def _open_and_supervise_one_cancels_all_nursery(
 | 
					 | 
				
			||||||
    actor: Actor,
 | 
					 | 
				
			||||||
) -> typing.AsyncGenerator[ActorNursery, None]:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # the collection of errors retreived from spawned sub-actors
 | 
					 | 
				
			||||||
    errors: Dict[Tuple[str, str], Exception] = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # This is the outermost level "deamon actor" nursery. It is awaited
 | 
					 | 
				
			||||||
    # **after** the below inner "run in actor nursery". This allows for
 | 
					 | 
				
			||||||
    # 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()``).
 | 
					 | 
				
			||||||
    original_err = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # errors from this daemon actor nursery bubble up to caller
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        async with trio.open_nursery() as da_nursery:
 | 
					 | 
				
			||||||
            # try:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # This is the inner level "run in actor" nursery. It is
 | 
					 | 
				
			||||||
            # awaited first since actors spawned in this way (using
 | 
					 | 
				
			||||||
            # ``ActorNusery.run_in_actor()``) are expected to only
 | 
					 | 
				
			||||||
            # return a single result and then complete (i.e. be canclled
 | 
					 | 
				
			||||||
            # gracefully). Errors collected from these actors are
 | 
					 | 
				
			||||||
            # immediately raised for handling by a supervisor strategy.
 | 
					 | 
				
			||||||
            # As such if the strategy propagates any error(s) upwards
 | 
					 | 
				
			||||||
            # the above "daemon actor" nursery will be notified.
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                async with trio.open_nursery() as ria_nursery:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    anursery = ActorNursery(
 | 
					 | 
				
			||||||
                        actor,
 | 
					 | 
				
			||||||
                        ria_nursery,
 | 
					 | 
				
			||||||
                        da_nursery,
 | 
					 | 
				
			||||||
                        errors
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    # spawning of actors happens in the caller's scope
 | 
					 | 
				
			||||||
                    # after we yield upwards
 | 
					 | 
				
			||||||
                    yield anursery
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    log.runtime(
 | 
					 | 
				
			||||||
                        f"Waiting on subactors {anursery._children} "
 | 
					 | 
				
			||||||
                        "to complete"
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # signal all process monitor tasks to conduct
 | 
					 | 
				
			||||||
                    # hard join phase.
 | 
					 | 
				
			||||||
                    # await maybe_wait_for_debugger()
 | 
					 | 
				
			||||||
                    # log.error('joing trigger NORMAL')
 | 
					 | 
				
			||||||
                    anursery._join_procs.set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            except BaseException as err:
 | 
					 | 
				
			||||||
                original_err = err
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # XXX: hypothetically an error could be
 | 
					        # XXX: hypothetically an error could be
 | 
				
			||||||
        # raised and then a cancel signal shows up
 | 
					        # raised and then a cancel signal shows up
 | 
				
			||||||
        # slightly after in which case the `else:`
 | 
					        # slightly after in which case the `else:`
 | 
				
			||||||
| 
						 | 
					@ -276,21 +232,108 @@ async def _open_and_supervise_one_cancels_all_nursery(
 | 
				
			||||||
                    f"errored with {err}, ")
 | 
					                    f"errored with {err}, ")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # cancel all subactors
 | 
					            # cancel all subactors
 | 
				
			||||||
                    await anursery.cancel()
 | 
					            await self.cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # ria_nursery scope end - nursery checkpoint
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # after daemon nursery exit
 | 
					@asynccontextmanager
 | 
				
			||||||
 | 
					async def _open_and_supervise_one_cancels_all_nursery(
 | 
				
			||||||
 | 
					    actor: Actor,
 | 
				
			||||||
 | 
					) -> typing.AsyncGenerator[ActorNursery, None]:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # the collection of errors retreived from spawned sub-actors
 | 
				
			||||||
 | 
					    errors: Dict[Tuple[str, str], Exception] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # This is the outermost level "deamon actor" nursery. It is awaited
 | 
				
			||||||
 | 
					    # **after** the below inner "run in actor nursery". This allows for
 | 
				
			||||||
 | 
					    # 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()``).
 | 
				
			||||||
 | 
					    src_err: Optional[BaseException] = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # errors from this daemon actor nursery bubble up to caller
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        async with trio.open_nursery() as spawn_n:
 | 
				
			||||||
 | 
					            # try:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # This is the inner level "run in actor" nursery. It is
 | 
				
			||||||
 | 
					            # awaited first since actors spawned in this way (using
 | 
				
			||||||
 | 
					            # ``ActorNusery.run_in_actor()``) are expected to only
 | 
				
			||||||
 | 
					            # return a single result and then complete (i.e. be canclled
 | 
				
			||||||
 | 
					            # gracefully). Errors collected from these actors are
 | 
				
			||||||
 | 
					            # immediately raised for handling by a supervisor strategy.
 | 
				
			||||||
 | 
					            # As such if the strategy propagates any error(s) upwards
 | 
				
			||||||
 | 
					            # the above "daemon actor" nursery will be notified.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            anursery = ActorNursery(
 | 
				
			||||||
 | 
					                actor,
 | 
				
			||||||
 | 
					                spawn_n,
 | 
				
			||||||
 | 
					                errors
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            # spawning of actors happens in the caller's scope
 | 
				
			||||||
 | 
					            # after we yield upwards
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                yield anursery
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                log.runtime(
 | 
				
			||||||
 | 
					                    f"Waiting on subactors {anursery._children} "
 | 
				
			||||||
 | 
					                    "to complete"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # signal all process monitor tasks to conduct
 | 
				
			||||||
 | 
					                # hard join phase.
 | 
				
			||||||
 | 
					                # await maybe_wait_for_debugger()
 | 
				
			||||||
 | 
					                # log.error('joing trigger NORMAL')
 | 
				
			||||||
 | 
					                anursery._join_procs.set()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # NOTE: there are 2 cases for error propagation:
 | 
				
			||||||
 | 
					            # - an actor which is ``.run_in_actor()`` invoked
 | 
				
			||||||
 | 
					            #   runs a single task and reports the error upwards
 | 
				
			||||||
 | 
					            # - the top level task which opened this nursery (in the
 | 
				
			||||||
 | 
					            #   parent actor) raises. In this case the raise can come
 | 
				
			||||||
 | 
					            #   from a variety of places:
 | 
				
			||||||
 | 
					            #   - user task code unrelated to the nursery/child actors
 | 
				
			||||||
 | 
					            #   - a ``RemoteActorError`` propagated up through the
 | 
				
			||||||
 | 
					            #   portal api from a child actor which will look the exact
 | 
				
			||||||
 | 
					            #   same as a user code failure.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            except BaseException as err:
 | 
				
			||||||
 | 
					                print('ERROR')
 | 
				
			||||||
 | 
					                # anursery._join_procs.set()
 | 
				
			||||||
 | 
					                src_err = err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # with trio.CancelScope(shield=True):
 | 
				
			||||||
 | 
					                await anursery._handle_err(err)
 | 
				
			||||||
 | 
					                raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except BaseException as err:
 | 
				
			||||||
 | 
					        # nursery bubble up
 | 
				
			||||||
 | 
					        nurse_err = err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # do not double cancel subactors
 | 
				
			||||||
 | 
					        if not anursery.cancelled:
 | 
				
			||||||
 | 
					            await anursery._handle_err(err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    finally:
 | 
					    finally:
 | 
				
			||||||
 | 
					        if anursery._children:
 | 
				
			||||||
            log.cancel(f'Waiting on remaining children {anursery._children}')
 | 
					            log.cancel(f'Waiting on remaining children {anursery._children}')
 | 
				
			||||||
            with trio.CancelScope(shield=True):
 | 
					            with trio.CancelScope(shield=True):
 | 
				
			||||||
                await anursery._all_children_reaped.wait()
 | 
					                await anursery._all_children_reaped.wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log.cancel(f'All children complete for {anursery}')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # No errors were raised while awaiting ".run_in_actor()"
 | 
					        # No errors were raised while awaiting ".run_in_actor()"
 | 
				
			||||||
        # actors but those actors may have returned remote errors as
 | 
					        # actors but those actors may have returned remote errors as
 | 
				
			||||||
        # results (meaning they errored remotely and have relayed
 | 
					        # results (meaning they errored remotely and have relayed
 | 
				
			||||||
        # those errors back to this parent actor). The errors are
 | 
					        # those errors back to this parent actor). The errors are
 | 
				
			||||||
        # collected in ``errors`` so cancel all actors, summarize
 | 
					        # collected in ``errors`` so cancel all actors, summarize
 | 
				
			||||||
        # all errors and re-raise.
 | 
					        # all errors and re-raise.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if src_err and src_err not in errors.values():
 | 
				
			||||||
 | 
					            errors[actor.uid] = src_err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if errors:
 | 
					        if errors:
 | 
				
			||||||
            if anursery._children:
 | 
					            if anursery._children:
 | 
				
			||||||
                raise RuntimeError("WHERE TF IS THE ZOMBIE LORD!?!?!")
 | 
					                raise RuntimeError("WHERE TF IS THE ZOMBIE LORD!?!?!")
 | 
				
			||||||
| 
						 | 
					@ -306,8 +349,8 @@ async def _open_and_supervise_one_cancels_all_nursery(
 | 
				
			||||||
        log.cancel(f'{anursery} terminated gracefully')
 | 
					        log.cancel(f'{anursery} terminated gracefully')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # XXX" honestly no idea why this is needed but sure..
 | 
					        # XXX" honestly no idea why this is needed but sure..
 | 
				
			||||||
        if isinstance(original_err, KeyboardInterrupt) and anursery.cancelled:
 | 
					        if isinstance(src_err, KeyboardInterrupt) and anursery.cancelled:
 | 
				
			||||||
            raise original_err
 | 
					            raise src_err
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@asynccontextmanager
 | 
					@asynccontextmanager
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue