From 5f55c7ca003cfb99555122796acbda9b61748652 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 15 Nov 2020 23:54:42 -0500 Subject: [PATCH] Add `Actor.cancel_soon()` for sync self destruct Add a sync method that can be used to cancel the current actor from a synchronous context. This is useful in debugging situations where sync debugger code may need to kill the process tree. Also, make the internal "lifetime stack" a global var; easier to manage from client code that may was to add callbacks prior to the actor runtime being fully setup. --- tractor/_actor.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tractor/_actor.py b/tractor/_actor.py index 6d223b0..8ed3e4a 100644 --- a/tractor/_actor.py +++ b/tractor/_actor.py @@ -162,6 +162,10 @@ def _get_mod_abspath(module): return os.path.abspath(module.__file__) +# process-global stack closed at end on actor runtime teardown +_lifetime_stack: ExitStack = ExitStack() + + class Actor: """The fundamental concurrency primitive. @@ -176,7 +180,6 @@ class Actor: _root_n: Optional[trio.Nursery] = None _service_n: Optional[trio.Nursery] = None _server_n: Optional[trio.Nursery] = None - _lifetime_stack: ExitStack = ExitStack() # Information about `__main__` from parent _parent_main_data: Dict[str, str] @@ -531,8 +534,9 @@ class Actor: # deadlock and other weird behaviour) if func != self.cancel: if isinstance(cs, Exception): - log.warning(f"Task for RPC func {func} failed with" - f"{cs}") + log.warning( + f"Task for RPC func {func} failed with" + f"{cs}") else: # mark that we have ongoing rpc tasks self._ongoing_rpc_tasks = trio.Event() @@ -770,7 +774,7 @@ class Actor: # tear down all lifetime contexts # api idea: ``tractor.open_context()`` log.warning("Closing all actor lifetime contexts") - self._lifetime_stack.close() + _lifetime_stack.close() # Unregister actor from the arbiter if registered_with_arbiter and ( @@ -840,6 +844,14 @@ class Actor: # signal the server is down since nursery above terminated self._server_down.set() + def cancel_soon(self) -> None: + """Cancel this actor asap; can be called from a sync context. + + Schedules `.cancel()` to be run immediately just like when + cancelled by the parent. + """ + self._service_n.start_soon(self.cancel) + async def cancel(self) -> bool: """Cancel this actor.