tractor#

distributed structured concurrency

a multi-processing runtime built on (and shaped entirely like) trio.

tractor provides parallelism via trio “actors”:

  • independent Python processes each running a trio task tree,

  • all composed into a distributed supervision tree with end-to-end SC,

  • spawning, cancellation, error propagation and teardown that work across processes (and hosts) exactly the way they work across tasks.

Sixty seconds of why#

Spawn one actor per core, open a Context into each — the child started()-handshakes its name and pid back — then crash the root on purpose and watch the runtime contain the blast: errors propagate, every child is reaped, zero zombies — guaranteed (it’s a bug otherwise).

examples/parallelism/we_are_processes.py#
'''
Run with a process monitor from a terminal using::

    $TERM -e watch -n 0.1  "pstree -a $$" \
        & python examples/parallelism/we_are_processes.py \
        && kill $!

'''
from multiprocessing import cpu_count
import os

import tractor
import trio


@tractor.context
async def endpoint(
    ctx: tractor.Context,
):
    actor_name: str = tractor.current_actor().name
    pid: int = os.getpid()
    await ctx.started((actor_name, pid))
    await trio.sleep_forever()


async def spawn_and_open_ep(
    an: tractor.ActorNursery,
    i: int,
) -> None:
    '''
    Spawn a subactor, start a remote `endpoint()`-task in it.

    '''
    ptl: tractor.Portal = await an.start_actor(
        name=f'worker_{i}',
        enable_modules=[__name__],
    )
    ctx: tractor.Context
    async with ptl.open_context(endpoint) as (
        ctx,
        (sub_name, sub_pid),
    ):
        print(
            f'Started ep-task in subactor,\n'
            f'{i}::{sub_name!r}@{sub_pid}\n'
        )
        await ctx.wait_for_result()


async def main():
    '''
    Spawn a subactor-per-CPU then self-destruct the cluster.

    '''
    tn: trio.Nursery
    an: tractor.ActorNursery
    async with (
        tractor.open_nursery(
            # XXX coming soon!
            # https://github.com/goodboy/tractor/pull/463
            # start_method='main_thread_forkserver',
        ) as an,
        # spawn subs concurrently (in bg `trio.Task`s) so each
        # actor's cold `import tractor` (~0.4s, see #470) overlaps
        # instead of stacking; once forkserver (#463) lands, spawn
        # is cheap enough to just loop sequentially.
        trio.open_nursery() as tn,
    ):
        for i in range(cpu_count()):
            tn.start_soon(
                spawn_and_open_ep,
                an,
                i,
            )
        destruct_in: int = 2
        print(
            f'This tree will self-destruct in {destruct_in}s..\n'
        )
        await trio.sleep(destruct_in)
        raise Exception('Self Destructed')


if __name__ == '__main__':
    try:
        trio.run(main)
    except Exception:
        print('Zombies Contained')

Like every snippet in these docs this file lives in the repo’s examples/ dir and runs under CI — docs code that can’t rot.

Dig in#

Get started

Install + your first actor tree in ~20 lines; causality, daemons and the trynamic scene.

Getting started
The big ideas

SC across processes, distilled — then the runtime architecture under it.

Structured concurrency, across processes
Debug like a local

await tractor.pause() anywhere in the tree: one terminal, every process, zero socket-juggling.

“Native” multi-process debugging
Streaming + contexts

Bidirectional, cancellation-safe msg streams between any two actors.

The Context: a cross-actor task pair
Guides

RPC, supervision, clustering, “infected asyncio”, typed msging + more.

Guides
API reference

The curated public surface; everything importable from tractor.

API reference

Features#

  • It’s just a trio API — same nursery discipline, same cancellation semantics, one level up the process tree.

  • Infinitely nestable process trees: sub-actors can spawn sub-actors, supervision stays transitive.

  • A “native UX” multi-process debugger REPL: built on pdbp with tree-wide tty locking (see “Native” multi-process debugging).

  • Built-in, cancellation-safe bidirectional streaming via a cheap or nasty (un)protocol.

  • Typed IPC: msgspec-backed wire msgs with optional per-dialog payload specs (Typed messaging).

  • Swappable process-spawn backends + modular IPC transports (TCP today, UDS on same-host, more planned).

  • Optionally distributed: the same APIs work over multiple hosts as on multiple cores.

  • Infected asyncio” mode: SC-supervise asyncio tasks from trio (Infected asyncio).

  • trio extension goodies via tractor.trionics (acm gathering, single-resource caching, broadcast channels).

Where do i start!?#

The first step to grok tractor is to get an intermediate knowledge of trio and structured concurrency B)

Some great places to start are,

then come back and hit Quickstart.