Runtime and spawning#
The core lifecycle API: boot the runtime in your root process,
spawn trio-“actors” (processes running trio.run() task trees)
under a one-cancels-all supervisor, and talk to them through
portals. This is structured concurrency (SC) applied transitively:
every spawned process is owned by a nursery block and errors
always propagate. If you can create zombies it is a bug.
Booting the runtime#
- tractor.open_root_actor(*, tpt_bind_addrs=None, registry_addrs=None, enable_transports=None, name='root', start_method=None, debug_mode=False, maybe_enable_greenback=False, enable_stack_on_sig=False, loglevel=None, enable_modules=None, rpc_module_paths=None, ensure_registry=False, hide_tb=True, debug_filter=<function <lambda>>)[source]#
Initialize the tractor runtime by starting a “root actor” in a parent-most Python process.
All (disjoint) actor-process-trees-as-programs are created via this entrypoint.
- Parameters:
tpt_bind_addrs (list[Address | tuple[str, int | str]] | None)
registry_addrs (list[Address | tuple[str, int | str]] | None)
name (str | None)
start_method (Literal['trio', 'mp_spawn', 'mp_forkserver'] | None)
debug_mode (bool)
maybe_enable_greenback (bool)
enable_stack_on_sig (bool)
loglevel (str | None)
enable_modules (list | None)
rpc_module_paths (list | None)
ensure_registry (bool)
hide_tb (bool)
debug_filter (Callable[[BaseException | BaseExceptionGroup], bool])
- Return type:
Note
The env vars TRACTOR_LOGLEVEL and TRACTOR_SPAWN_METHOD
override the loglevel / start_method params so you can
crank verbosity or swap spawn backends without touching app
code. Exactly one IPC transport may be enabled per actor
(see enable_transports and IPC and logging).
- tractor.run_daemon(enable_modules, name='root', registry_addrs=None, start_method=None, debug_mode=False, **kwargs)[source]#
Spawn a root (daemon) actor which will respond to RPC; the main task simply starts the runtime and then blocks via embedded trio.sleep_forever().
This is a very minimal convenience wrapper around starting a “run-until-cancelled” root actor which can be started with a set of enabled modules for RPC request handling.
Spawning actors#
A supervised actor (process) tree.#
- tractor.open_nursery(*, hide_tb=True, **kwargs)[source]#
Create and yield a new
ActorNurseryto be used for spawning structured concurrent subactors.When an actor is spawned a new trio task is started which invokes one of the process spawning backends to create and start a new subprocess. These tasks are started by one of two nurseries detailed below. The reason for spawning processes from within a new task is because
trio_run_in_processitself creates a new internal nursery and the same task that opens a nursery must close it. It turns out this approach is probably more correct anyway since it is more clear from the following nested nurseries which cancellation scopes correspond to each spawned subactor set.- Parameters:
hide_tb (bool)
- Return type:
AsyncGenerator[ActorNursery, None]
- class tractor.ActorNursery(actor, ria_nursery, da_nursery, errors)[source]#
The fundamental actor supervision construct: spawn and manage explicit lifetime and capability restricted, bootstrapped,
trio.run()scheduled sub-processes.Though the concept of a “process nursery” is different in complexity and slightly different in semantics then a tradtional single threaded task nursery, much of the interface is the same. New processes each require a top level “parent” or “root” task which is itself no different then any task started by a tradtional
trio.Nursery. The main difference is that each “actor” (a process +trio.run()) contains a full, paralell executingtrio-task-tree. The following super powers ensue:starting tasks in a child actor are completely independent of tasks started in the current process. They execute in parallel relative to tasks in the current process and are scheduled by their own actor’s
triorun loop.tasks scheduled in a remote process still maintain an SC protocol across memory boundaries using a so called “structured concurrency dialogue protocol” which ensures task-hierarchy-lifetimes are linked.
remote tasks (in another actor) can fail and relay failure back to the caller task (in some other actor) via a seralized
RemoteActorErrorwhich means no zombie process or RPC initiated task can ever go off on its own.
- Parameters:
- property cancel_called: 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.
- property cancelled_caught: bool#
Set when this nursery was able to cance all spawned subactors gracefully via an (implicit) call to .cancel().
- async start_actor(name, *, bind_addrs=None, rpc_module_paths=None, enable_transports=['tcp'], enable_modules=None, loglevel=None, debug_mode=None, infect_asyncio=False, inherit_parent_main=True, nursery=None, proc_kwargs=None)[source]#
Start a (daemon) actor: an process that has no designated “main task” besides the runtime.
Pass
inherit_parent_main=Falseto keep this child on its own bootstrap module for the trio spawn backend instead of applying the parent__main__re-exec fixup during startup. This does not affectmultiprocessingspawnorforkserverwhich reconstruct the parent’s__main__as part of their normal stdlib bootstrap.
- async run_in_actor(fn, *, name=None, bind_addrs=None, rpc_module_paths=None, enable_modules=None, loglevel=None, infect_asyncio=False, inherit_parent_main=True, proc_kwargs=None, **kwargs)[source]#
Spawn a new actor, run a lone task, then terminate the actor and return its result.
Actors spawned using this method are kept alive at nursery teardown until the task spawned by executing
fncompletes at which point the actor is terminated.
- async cancel(hard_kill=False)[source]#
Cancel this actor-nursery by instructing each subactor’s runtime to cancel and wait for all underlying sub-processes to terminate.
If hard_kill is set then kill the processes directly using the spawning-backend’s API/OS-machinery without any attempt at (graceful) trio-style cancellation using our Actor.cancel().
- Parameters:
hard_kill (bool)
- Return type:
None
Note
ActorNursery.start_actor() (daemon actor + portal) is the
blessed spawning primitive; pair it with
Portal.open_context() for SC-linked remote tasks.
ActorNursery.run_in_actor() is a convenience one-shot —
spawn, run a single task, auto-cancel after the result — slated
to be rebuilt as a high-level wrapper, so don’t design around
it as the core model.
Deprecated since version 0.1.0a6: ActorNursery.cancelled warns; use
ActorNursery.cancel_called and
ActorNursery.cancelled_caught. The rpc_module_paths
kwarg is likewise deprecated in favor of enable_modules.
Portals#
A Portal “opens a portal” into a peer actor’s memory
domain: you call functions and start SC-linked tasks over IPC as
though they were local, with results, errors and cancellation
flowing back exactly like trio.
- class tractor.Portal(channel)[source]#
A ‘portal’ to a memory-domain-separated Actor.
A portal is “opened” (and eventually closed) by one side of an inter-actor communication context. The side which opens the portal is equivalent to a “caller” in function parlance and usually is either the called actor’s parent (in process tree hierarchy terms) or a client interested in scheduling work to be done remotely in a process which has a separate (virtual) memory domain.
The portal api allows the “caller” actor to invoke remote routines and receive results through an underlying
tractor.Channelas though the remote (async) function / generator was called locally. It may be thought of loosely as an RPC api where native Python function calling semantics are supported transparently; hence it is like having a “portal” between the seperate actor memory spaces.- Parameters:
channel (Channel)
- async wait_for_result(hide_tb=True)[source]#
Return the final result delivered by a Return-msg from the remote peer actor’s “main” task’s return statement.
- async cancel_actor(timeout=None, raise_on_timeout=False)[source]#
Cancel the actor runtime (and thus process) on the far end of this portal.
NOTE THIS CANCELS THE ENTIRE RUNTIME AND THE SUBPROCESS, it DOES NOT just cancel the remote task. If you want to have a handle to cancel a remote
tri.Tasklook at .open_context() and the definition of ._context.Context.cancel() which CAN be used for this purpose.raise_on_timeout (default False):
False (legacy): on bounded-wait expiry, log at DEBUG and return False. Used by callers that issue cancel fire-and-forget and have their own escalation (e.g. _spawn.soft_kill() checks proc.poll() after).
True: on bounded-wait expiry, raise ActorTooSlowError so the caller MUST handle the failure explicitly. ActorNursery.cancel() opts in so it can escalate via proc.terminate() per SC-discipline.
- async run_from_ns(namespace_path, function_name, **kwargs)[source]#
Run a function from a (remote) namespace in a new task on the far-end actor.
This is a more explitcit way to run tasks in a remote-process actor using explicit object-path syntax. Hint: this is how .run() works underneath.
Note:
A special namespace `self` can be used to invoke `Actor` instance methods in the remote runtime. Currently this should only ever be used for `Actor` (method) runtime internals!
- async run(func, fn_name=None, **kwargs)[source]#
Submit a remote function to be scheduled and run by actor, in a new task, wrap and return its (stream of) result(s).
This is a blocking call and returns either a value from the remote rpc task or a local async generator instance.
- open_stream_from(async_gen_func, **kwargs)[source]#
Legacy one-way streaming API.
TODO: re-impl on top Portal.open_context() + an async gen around Context.open_stream().
- Parameters:
async_gen_func (Callable)
- Return type:
AsyncGenerator[MsgStream, None]
Deprecated since version 0.1.0a6: Portal.result() warns; use Portal.wait_for_result().
The str-form Portal.run('mod.path', 'fn_name') also warns;
pass a function object whose module is listed in the target’s
enable_modules. Portal.channel is the legacy spelling
of Portal.chan.
- tractor._context.open_context_from_portal(portal, func, allow_overruns=False, hide_tb=True, **kwargs)[source]#
Open an inter-actor “task context”; a remote task is scheduled and cancel-scope-state-linked to a trio.run() across memory boundaries in another actor’s runtime.
This is an @acm API bound as Portal.open_context() which allows for deterministic setup and teardown of a remotely scheduled task in another remote actor. Once opened, the 2 now “linked” tasks run completely in parallel in each actor’s runtime with their enclosing trio.CancelScopes kept in a synced state wherein if either side errors or cancels an equivalent error is relayed to the other side via an SC-compat IPC protocol.
The yielded tuple is a pair delivering a tractor.Context and any first value “sent” by the “child” task via a call to Context.started(<value: Any>); this side of the context does not unblock until the “child” task calls .started() in similar style to trio.Nursery.start(). When the “child” (side that is “called”/started by a call to this method) returns, the parent side (this) unblocks and any final value delivered from the other end can be retrieved using the Contex.wait_for_result() api.
The yielded
Contextinstance further allows for opening bidirectional streams, explicit cancellation and structurred-concurrency-synchronized final result-msg collection. Seetractor.Contextfor more details.
Note
open_context_from_portal() is bound as
the method-alias Portal.open_context() — that’s the
spelling you should actually call:
portal.open_context(fn, **kwargs). See Contexts and streaming
for the full Context + MsgStream API it unlocks.
Note
Portal.cancel_actor() cancels the whole remote runtime
and process (machine-level), not a single task — use
Context.cancel() for task-level cancellation. Pass
raise_on_timeout=True to get an ActorTooSlowError you
can escalate per SC discipline (see Errors and cancellation types).
Clusters#
- tractor.open_actor_cluster(modules, count=4, names=None, hard_kill=False, **runtime_kwargs)[source]#
Spawn a flat cluster of count worker actors (default: one
per core) all serving the RPC modules list, yielding a
dict[str, Portal] keyed by actor name. Handy for
embarrassingly parallel fan-out; see examples/quick_cluster.py.
Runtime introspection#
- class tractor.Actor(name, uuid, *, enable_modules=None, loglevel=None, registry_addrs=None, spawn_method=None, inherit_parent_main=True, arbiter_addr=None)[source]#
The fundamental “runtime” concurrency primitive.
An “actor” is the combination of a regular Python process executing a trio.run() task tree, communicating with other “actors” through “memory boundary portals”: Portal, which provide a high-level async API around IPC “channels” (Channel) which themselves encapsulate various (swappable) network transport protocols for sending msgs between said memory domains (processes, hosts, non-GIL threads).
Each “actor” is trio.run() scheduled “runtime” composed of many concurrent tasks in a single thread. The “runtime” tasks conduct a slew of low(er) level functions to make it possible for message passing between actors as well as the ability to create new actors (aka new “runtimes” in new processes which are supervised via an “actor-nursery” construct). Each task which sends messages to a task in a “peer” actor (not necessarily a parent-child, depth hierarchy) is able to do so via an “address”, which maps IPC connections across memory boundaries, and a task request id which allows for per-actor tasks to send and receive messages to specific peer-actor tasks with which there is an ongoing RPC/IPC dialog.
- Parameters:
- property aid: Aid#
This process-singleton-actor’s “unique actor ID” in struct form.
See the tractor.msg.Aid struct for details.
- property uid: tuple[str, str]#
This process-singleton’s “unique (cross-host) ID”.
Delivered from the .Aid.name/.uuid fields as a tuple pair and should be multi-host unique despite a large distributed process plane.
Note
Actor is the per-process runtime singleton (msg loop,
RPC scheduling, IPC server) — you never instantiate it yourself
and should normally only touch the identity/introspection
surface listed above. The canonical identity type is
Actor.aid (a tractor.msg.Aid struct);
Actor.uid is the legacy (name, uuid) 2-tuple which
is still pervasive in logs and error metadata.
Deprecated since version 0.1.0a6: Actor.is_arbiter warns; use Actor.is_registrar.
The arbiter_addr constructor kwarg is deprecated for
registry_addrs.
- tractor.get_runtime_vars(as_dict=True, clear_values=False)[source]#
Deliver a copy of the current Actor’s “runtime variables”.
By default, for historical impl reasons, this delivers the dict form, but the RuntimeVars struct should be utilized as possible for future calls.
Pure read — never mutates the module-level _runtime_vars.
If clear_values=True, return a copy of the fresh-process defaults (_RUNTIME_VARS_DEFAULTS) instead of the live dict. Useful in combination with set_runtime_vars() to reset process-global state back to “cold” — the main caller today is the main_thread_forkserver spawn backend’s post-fork child prelude:
set_runtime_vars(get_runtime_vars(clear_values=True))
os.fork() inherits the parent’s full memory image, so the child sees the parent’s populated _runtime_vars (e.g. _is_root=True) which would trip the assert not self.enable_modules gate in Actor._from_parent() on the subsequent parent→child SpawnSpec handshake if left alone.
See also
Contexts and streaming for the SC-linked remote task API, Discovery and the registrar for finding actors by name, and the guided tours in Spawning actors, RPC: calling into other actors and The Context: a cross-actor task pair.