From 4c3c3e4b565637cd4ee9213501c0bf6a55a3b2dd Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 8 Mar 2024 14:11:17 -0500 Subject: [PATCH] Support a `._state.last_actor()` getter Not sure if it's really that useful other then for reporting errors from `current_actor()` but at least it alerts `tractor` devs and/or users when the runtime has already terminated vs. hasn't been started yet/correctly. Set the `._last_actor_terminated: tuple` in the root's final block which allows testing for an already terminated tree which is the case where `._state._current_actor == None` and the last is set. --- tractor/_root.py | 1 + tractor/_state.py | 53 ++++++++++++++++++++++++++++++++++++++----- tractor/_supervise.py | 11 +++++---- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/tractor/_root.py b/tractor/_root.py index 1d3d4f1..f948913 100644 --- a/tractor/_root.py +++ b/tractor/_root.py @@ -348,6 +348,7 @@ async def open_root_actor( await actor.cancel(None) # self cancel finally: _state._current_actor = None + _state._last_actor_terminated = actor # restore built-in `breakpoint()` hook state sys.breakpointhook = builtin_bp_handler diff --git a/tractor/_state.py b/tractor/_state.py index f94c3eb..f391743 100644 --- a/tractor/_state.py +++ b/tractor/_state.py @@ -18,12 +18,18 @@ Per process state """ +from __future__ import annotations from typing import ( - Optional, Any, + TYPE_CHECKING, ) -_current_actor: Optional['Actor'] = None # type: ignore # noqa +if TYPE_CHECKING: + from ._runtime import Actor + + +_current_actor: Actor|None = None # type: ignore # noqa +_last_actor_terminated: Actor|None = None _runtime_vars: dict[str, Any] = { '_debug_mode': False, '_is_root': False, @@ -31,14 +37,49 @@ _runtime_vars: dict[str, Any] = { } -def current_actor(err_on_no_runtime: bool = True) -> 'Actor': # type: ignore # noqa +def last_actor() -> Actor|None: + ''' + Try to return last active `Actor` singleton + for this process. + + For case where runtime already exited but someone is asking + about the "last" actor probably to get its `.uid: tuple`. + + ''' + return _last_actor_terminated + + +def current_actor( + err_on_no_runtime: bool = True, +) -> Actor: ''' Get the process-local actor instance. ''' - from ._exceptions import NoRuntime - if _current_actor is None and err_on_no_runtime: - raise NoRuntime("No local actor has been initialized yet") + if ( + err_on_no_runtime + and _current_actor is None + ): + msg: str = 'No local actor has been initialized yet' + from ._exceptions import NoRuntime + + if last := last_actor(): + msg += ( + f'Apparently the lact active actor was\n' + f'|_{last}\n' + f'|_{last.uid}\n' + ) + # no actor runtime has (as of yet) ever been started for + # this process. + else: + msg += ( + 'No last actor found?\n' + 'Did you forget to open one of:\n\n' + '- `tractor.open_root_actor()`\n' + '- `tractor.open_nursery()`\n' + ) + + raise NoRuntime(msg) return _current_actor diff --git a/tractor/_supervise.py b/tractor/_supervise.py index c27e0e4..ad007ae 100644 --- a/tractor/_supervise.py +++ b/tractor/_supervise.py @@ -533,12 +533,15 @@ async def open_nursery( ''' implicit_runtime: bool = False - - actor = current_actor(err_on_no_runtime=False) + actor: Actor = current_actor( + err_on_no_runtime=False + ) try: - if actor is None and is_main_process(): - + if ( + actor is None + and is_main_process() + ): # if we are the parent process start the # actor runtime implicitly log.info("Starting actor runtime!")