tractor/docs/guide/clustering.rst

4.9 KiB

Higher-level cluster APIs

Sometimes you don't want a hand-crafted supervision tree; you want "a pile of workers, one per core, now please". For that there's tractor.open_actor_cluster: a convenience wrapper which spawns a flat cluster of subactors and hands you back a portal to each,

@acm
async def open_actor_cluster(
    modules: list[str],            # RPC allowlist for workers
    count: int = cpu_count(),      # one per core by default
    names: list[str]|None = None,  # default: 'worker_{i}'
    hard_kill: bool = False,       # fwd to `an.cancel()`
    **runtime_kwargs,              # fwd to `open_root_actor()`
) -> AsyncGenerator[dict[str, tractor.Portal], None]:

A cluster in one block

../../examples/quick_cluster.py

Walkthrough,

  • open_actor_cluster(modules=[__name__]) concurrently spawns one subactor per detected core (per multiprocessing.cpu_count); the modules list is the usual enable_modules-style capability allowlist so workers may run functions defined in this module,
  • it yields a dict[str, tractor.Portal] mapping worker name to portal; note the keys get prefixed with the spawning actor's name, so from the root you'll see 'root.worker_0', 'root.worker_1', etc.,
  • a plain trio.Nursery then fans out one portal.run(sleepy_jane) per worker; each prints its actor .uid from inside its own process then naps forever — what runs inside each worker (and how many tasks you point at it) is entirely yours to compose,
  • tractor.trionics.collapse_eg() un-nests the strict ExceptionGroup wrapping so the demo's KeyboardInterrupt surfaces as itself instead of arriving eg-boxed,
  • on block exit the whole fleet is torn down for you via tractor.ActorNursery.cancel; pass hard_kill=True at open time to skip straight to OS-level termination instead of the graceful ladder described in /guide/cancellation.

Sizing, naming, fleet-wide options

count doesn't have to be core-count and the auto-generated 'worker_{i}' names are just the default; pass your own (the length must match count or you get a ValueError). Any extra **runtime_kwargs pass through verbatim to tractor.open_root_actor, so fleet-wide runtime options are one kwarg away,

async with tractor.open_actor_cluster(
    modules=['mylib.workers'],
    count=4,
    names=['scout', 'miner', 'smelter', 'smith'],
    debug_mode=True,    # whole-fleet crash-to-REPL
) as portal_map:
    ...

From here the composition patterns are the usual tractor fare: portal.run() for one-shot calls (as in the demo), or — for a persistent bidirectional dialog per worker — concurrently enter N portal.open_context() blocks with tractor.trionics.gather_contexts(); see /guide/context for that whole layer.

Clusters vs. nurseries

open_actor_cluster() is sugar, not a new primitive: under the hood it's just tractor.open_nursery plus N concurrent start_actor() calls plus a .cancel() on the way out. Reach for it when,

  • you want a flat, homogeneous fleet (classic worker-pool or map-style fan-out shapes),
  • "one per core" — or a fixed count — is the right sizing story,
  • every child can share the same spawn options.

Drop down to a raw tractor.ActorNursery when the topology gets any fancier: nested trees, heterogeneous children, per-child debug_mode/transport/module options, daemons mixed with one-shot workers, and so on (see /guide/parallelism for a hand-rolled pool). Either way the supervision semantics are identical: one-cancels-all error propagation and the no-zombies guarantee from /guide/cancellation apply to clusters too.

Provisional, by design

Note

APIs in this section are considered provisional: the signature and semantics of tractor.open_actor_cluster may shift as higher-level supervision machinery lands. We encourage you to try it and provide feedback — the matrix channel is the place to say hi, and #22 tracks the broader supervisor-strategy roadmap.

  • /guide/parallelism — worker pools built "by hand" with plain actor nurseries (and why that's easy peasy),
  • /guide/cancellation — the teardown machinery a cluster inherits for free.