Using eagle from several threads#
The rule is the same for every object that owns a run: one object, one caller at a time. Separate objects run in parallel without any care.
eagle.Graph, eagle.Simulation, eagle.Runner (from
eagle.until_done()) and the runner behind a dispatch= simulation
each hold a non-blocking guard. A second thread that enters while one call is
in flight gets eagle.Busy; it is never queued behind the first and
never handed a half-built state. The guard is a C++ atomic, so this holds on
free-threaded Python too.
import threading, eagle
# one runner per thread: runs side by side
def work(plan, planes):
runner = eagle.until_done(plan, max_steps=10_000, **planes)
runner.run()
# one runner shared by two threads: the second caller gets eagle.Busy
# unless it waits for the first to return
A graph’s runs stay ordered across threads: the first stage of each run waits
on the device for the run before it, whichever thread launched it. One
exception couples threads: a host-synchronized node (opaque(fn,
eager=True)) syncs the whole device, so it also waits for every other
thread’s GPU work.
Inside the package, module-level state is limited to read-only tables, locked tallies and idempotent memos (computing the same value twice is harmless); a test enforces it, so importing eagle from many threads is safe.
Free-threaded CPython (3.13t and 3.14t) is supported and tested in CI. See The graph model: eagle.Graph for the graph guard in context and Installation for the free-threaded install notes.