Wait for an instance to settle — the sync-over-async bridge
(GenDurable.await/3 is the public entry).
The answer is always read from the row (the single source of truth); everything else only shortens the time until that read:
- the local fast path — the executor, after committing a settled
outcome, nudges same-node waiters through an ETS table owned by the
watcher (one lookup per outcome; free when nobody waits). Waiters are
never
Process.linked to anything — an engine shutdown can strand a waiter, not kill it; - the watcher — a per-instance batched poller: waiters register their
ids, one
SELECT … WHERE id = ANY(...)per tick covers all of them (cross-node executions land within a tick); idle means zero queries; - with no running instance (bare-repo usage) a plain per-caller poll
loop, so
await/3works everywhereinsertdoes.
A nudge is only a hint — the waiter re-checks the row and keeps waiting if
it is not settled by its predicate. "Settled" means nothing left to do
without external input: done, failed, awaiting_signal,
awaiting_children (with until: :terminal, only the first two). Every
subscription point is guarded: an engine stopping or a component restarting
mid-await degrades the caller to polling (the row outlives the engine),
never crashes it — blocked waiters re-check and re-assert their watch on a
coarse cadence as the backstop.