From e05a4d3cac8f777a417dc1b073c1c000023e89ee Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 17 Jun 2025 12:31:36 -0400 Subject: [PATCH 1/3] Enforce named-args only to `.open_nursery()` --- tractor/_supervise.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tractor/_supervise.py b/tractor/_supervise.py index ec2b1864..68f1ac40 100644 --- a/tractor/_supervise.py +++ b/tractor/_supervise.py @@ -603,6 +603,7 @@ _shutdown_msg: str = ( @acm # @api_frame async def open_nursery( + *, # named params only! hide_tb: bool = True, **kwargs, # ^TODO, paramspec for `open_root_actor()` From c46986504ddc3a106a79b803e777674ad3bdeec9 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 15 Jul 2025 19:29:38 -0400 Subject: [PATCH 2/3] Switch nursery to `CancelScope`-status properties Been meaning to do this forever and a recent test hang finally drove me to it Bp Like it sounds, adopt the "cancel-status" properties on `ActorNursery` use already on our `Context` and derived from `trio.CancelScope`: - add new private `._cancel_called` (set in the head of `.cancel()`) & `._cancelled_caught` (set in the tail) instance vars with matching read-only `@properties`. - drop the instance-var and instead delegate a `.cancelled: bool` property to `._cancel_called` and add a usage deprecation warning (since removing it breaks a buncha tests). --- tractor/_supervise.py | 48 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/tractor/_supervise.py b/tractor/_supervise.py index 68f1ac40..be89c4cb 100644 --- a/tractor/_supervise.py +++ b/tractor/_supervise.py @@ -117,7 +117,6 @@ class ActorNursery: ] ] = {} - self.cancelled: bool = False self._join_procs = trio.Event() self._at_least_one_child_in_debug: bool = False self.errors = errors @@ -135,10 +134,53 @@ class ActorNursery: # TODO: remove the `.run_in_actor()` API and thus this 2ndary # nursery when that API get's moved outside this primitive! self._ria_nursery = ria_nursery + + # TODO, factor this into a .hilevel api! + # # portals spawned with ``run_in_actor()`` are # cancelled when their "main" result arrives self._cancel_after_result_on_exit: set = set() + # trio.Nursery-like cancel (request) statuses + self._cancelled_caught: bool = False + self._cancel_called: bool = False + + @property + def cancel_called(self) -> bool: + ''' + Records whether cancellation has been requested for this + actor-nursery by a call to `.cancel()` either due to, + - an explicit call by some actor-local-task, + - an implicit call due to an error/cancel emited inside + the `tractor.open_nursery()` block. + + ''' + return self._cancel_called + + @property + def cancelled_caught(self) -> bool: + ''' + Set when this nursery was able to cance all spawned subactors + gracefully via an (implicit) call to `.cancel()`. + + ''' + return self._cancelled_caught + + # TODO! remove internal/test-suite usage! + @property + def cancelled(self) -> bool: + warnings.warn( + "`ActorNursery.cancelled` is now deprecated, use " + " `.cancel_called` instead.", + DeprecationWarning, + stacklevel=2, + ) + return ( + self._cancel_called + # and + # self._cancelled_caught + ) + async def start_actor( self, name: str, @@ -316,7 +358,7 @@ class ActorNursery: ''' __runtimeframe__: int = 1 # noqa - self.cancelled = True + self._cancel_called = True # TODO: impl a repr for spawn more compact # then `._children`.. @@ -394,6 +436,8 @@ class ActorNursery: ) in children.values(): log.warning(f"Hard killing process {proc}") proc.terminate() + else: + self._cancelled_caught # mark ourselves as having (tried to have) cancelled all subactors self._join_procs.set() From 5c7d930a9a70248ab3d709835cef5562b5999fcb Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Sun, 10 Aug 2025 14:48:55 -0400 Subject: [PATCH 3/3] Drop unused `Actor._root_n`.. --- tractor/_runtime.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tractor/_runtime.py b/tractor/_runtime.py index ef7a3018..c9bd34bb 100644 --- a/tractor/_runtime.py +++ b/tractor/_runtime.py @@ -174,7 +174,6 @@ class Actor: msg_buffer_size: int = 2**6 # nursery placeholders filled in by `async_main()` after fork - _root_n: Nursery|None = None _service_n: Nursery|None = None _ipc_server: _server.IPCServer|None = None @@ -1479,8 +1478,8 @@ async def async_main( collapse_eg(), trio.open_nursery() as root_tn, ): - actor._root_n = root_tn - assert actor._root_n + # actor._root_n = root_tn + # assert actor._root_n ipc_server: _server.IPCServer async with (