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:
Return type:

Actor

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.

Parameters:
Return type:

None

Spawning actors#

root actor supervising a tree of subactors

A supervised actor (process) tree.#

tractor.open_nursery(*, hide_tb=True, **kwargs)[source]#

Create and yield a new ActorNursery to 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_process itself 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 executing trio-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 trio run 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 RemoteActorError which 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=False to 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 affect multiprocessing spawn or forkserver which reconstruct the parent’s __main__ as part of their normal stdlib bootstrap.

Parameters:
Return type:

Portal

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 fn completes at which point the actor is terminated.

Parameters:
Return type:

Portal

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.Channel as 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)

property chan: Channel#

Ref to this ctx’s underlying tractor.ipc.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.

Parameters:

hide_tb (bool)

Return type:

Any

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.Task look 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.

Parameters:
  • timeout (float | None)

  • raise_on_timeout (bool)

Return type:

bool

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!
Parameters:
  • namespace_path (str)

  • function_name (str)

Return type:

Any

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.

Parameters:
  • func (str)

  • fn_name (str | None)

Return type:

Any

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 Context instance further allows for opening bidirectional streams, explicit cancellation and structurred-concurrency-synchronized final result-msg collection. See tractor.Context for more details.

Parameters:
  • portal (Portal)

  • func (Callable)

  • allow_overruns (bool)

  • hide_tb (bool)

Return type:

AsyncGenerator[tuple[Context, Any], None]

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]#
Parameters:
Return type:

AsyncGenerator[dict[str, Portal], None]

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#

tractor.current_actor(err_on_no_runtime=True)[source]#

Get the process-local actor instance.

Parameters:

err_on_no_runtime (bool)

Return type:

Actor

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:
  • name (str)

  • uuid (str)

  • enable_modules (list[str] | None)

  • loglevel (str | None)

  • registry_addrs (list[Address] | None)

  • spawn_method (str | None)

  • inherit_parent_main (bool)

  • arbiter_addr (UnwrappedAddress | None)

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.

cancel_soon()[source]#

Cancel this actor asap; can be called from a sync context.

Schedules runtime cancellation via Actor.cancel() inside the RPC service nursery.

Return type:

None

is_infected_aio()[source]#

If True, this actor is running trio in guest mode on the asyncio event loop and thus can use the APIs in .to_asyncio to coordinate tasks running in each framework but within the same actor runtime.

Return type:

bool

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.current_ipc_ctx(error_on_not_set=False)[source]#
Parameters:

error_on_not_set (bool)

Return type:

Context | None

tractor.is_root_process()[source]#
Return type:

bool

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.

Parameters:
Return type:

dict

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.