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
triotask 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).
'''
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#
Install + your first actor tree in ~20 lines; causality, daemons and the trynamic scene.
SC across processes, distilled — then the runtime architecture under it.
await tractor.pause() anywhere in the
tree: one terminal, every process, zero
socket-juggling.
Bidirectional, cancellation-safe msg streams between any two actors.
RPC, supervision, clustering, “infected asyncio”, typed msging + more.
The curated public surface; everything
importable from tractor.
Features#
It’s just a
trioAPI — 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-superviseasynciotasks fromtrio(Infected asyncio).trioextension goodies viatractor.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,
the seminal blog post,
obviously the trio docs,
wikipedia’s nascent SC page,
the fancy diagrams @ libdill-docs,
then come back and hit Quickstart.