The per-instance limbo supervision for fence-surviving key servers
(design §8, implementation.md §7 fence modes): a DynamicSupervisor plus
the Fief.Key.Limbo.Tracker that manages it, supervised at the instance
root via Fief.Vnode.instance_children/1 (M7 Decision — previously an
unlinked island). The root is rest_for_one and the limbo is its first
child, so it survives crash-restarts of every kernel child (a crashed
Fief.Node fences the node — exactly when :continue survivors must keep
living) and dies only with the instance. The fence kill itself never
reaches here: it tears down agent link trees under Fief.Vnode.Manager.
Servers whose key module declares on_fence: :continue | :callback live
here from birth (Decision, M6): a running process cannot be re-parented
out of a DynamicSupervisor, and a supervisor terminates its children
gracefully even when its own parent is brutally killed — so
detach-at-fence is unimplementable, and placement at start is the honest
mechanism. The impl still holds the monitors and the key→pid map; normal
freeze/retire semantics are unchanged (servers stop themselves inside
extract_state/1 regardless of who supervises them).
What "marked for cleanup" means in v1:
- entries are tracked per
{key_id, vnode_id}and enumerable (entries/1) — fenced-but-alive:continueprocesses are findable; - a new impl incarnation for a vnode reaps that vnode's limbo entries
at
init/2(reap/2) — the node re-owning the vnode kills the stale survivors before serving (the reconnect-cleanup semantics of design §8 at the moment they matter for double-liveness on one node); - instance shutdown takes the whole subtree down through the root supervisor — nothing leaks.
Crash containment (:one_for_all here): the tracker and the
DynamicSupervisor share a fate — a crash of either restarts both into
an empty, mutually consistent state. The tracker's entry map is load-bearing
for safety: reap/2 is what stops a stale survivor from double-serving a
key, so an amnesiac tracker beside living survivors would leave them
unreapable — a double-serve hole. Coupling fates closes that hole with no
machinery: the restart is the recovery. Survivors die with the bookkeeper,
which is acceptable — :continue liveness is explicitly best-effort (it
forfeits guarantee 1 for side effects), survivors are crash-equivalent, and
reap/2 kills them unceremoniously anyway; a tracker crash is a minimal,
should-never-happen event. The rejected alternative — restart only the
tracker and rebuild its map by asking each survivor to identify itself —
was itself unsafe: the identity-call deadline would execute a healthy
survivor whose mailbox is merely flooded (the canonical :continue case, a
busy PubSub subscriber), so it never reliably bought the liveness it existed
to protect.
Recorded deferral: limbo processes for vnodes this node never re-owns
linger until the instance stops (full reconnect cleanup — killing on
observing lost ownership — is an M7+ refinement). That is inside
:continue's documented forfeit of guarantee 1 for side effects.
Summary
Functions
Returns a specification to start this module under a supervisor.
All limbo entries: [%{key: key_id, vnode: vnode_id, pid: pid}]. [] if no limbo.
The instance's limbo tracker name.
Kill every limbo entry belonging to vnode_id — called by a fresh impl
incarnation at init/2, so stale fence survivors never double-serve a key
on this node once it serves the vnode again. Raises (crashing that init)
if the tracker is mid-restart: the agent's own restart retries the reap —
skipping it silently could leave a survivor double-serving.
Start a limbo-supervised Fief.Key.Server. Same return shape as
DynamicSupervisor.start_child/2, plus {:error, {:limbo_unavailable, reason}} in the (supervision-window) case where the tracker is mid-restart
— the caller drops the triggering message, exactly like a refused start.
The instance's limbo DynamicSupervisor name.
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.
All limbo entries: [%{key: key_id, vnode: vnode_id, pid: pid}]. [] if no limbo.
The instance's limbo tracker name.
@spec reap(atom(), non_neg_integer()) :: :ok
Kill every limbo entry belonging to vnode_id — called by a fresh impl
incarnation at init/2, so stale fence survivors never double-serve a key
on this node once it serves the vnode again. Raises (crashing that init)
if the tracker is mid-restart: the agent's own restart retries the reap —
skipping it silently could leave a survivor double-serving.
@spec start_key_server(atom(), non_neg_integer(), term(), keyword()) :: {:ok, pid()} | :ignore | {:error, term()}
Start a limbo-supervised Fief.Key.Server. Same return shape as
DynamicSupervisor.start_child/2, plus {:error, {:limbo_unavailable, reason}} in the (supervision-window) case where the tracker is mid-restart
— the caller drops the triggering message, exactly like a refused start.
The instance's limbo DynamicSupervisor name.