View Source ProcessHub.Service.DeclaredChildren (ProcessHub v0.7.0)
The declared list: a versioned, durable, leader-written list of the children that SHALL exist on a hub.
Experimental
The declared-children feature is experimental and may change in future releases. Use in production at your own discretion.
start_child/3 with durable: true adds the child's spec, a deliberate stop
removes it, and nothing else writes it — list absence is the stop record and
never expires. Mutations serialize through the hub's leader (:elector,
with the lowest hub member as deterministic fallback), which bumps one
version per mutation; adoption replaces the whole list, higher version wins.
The list commits before the process action, so the orphan reconcile heals a
crashed command in the next round.
The list persists in its own DETS-backed store beside the registry, is
cached in misc storage for reads, and optionally ships to an off-cluster
ProcessHub.Storage.RemoteManifest. A missing or corrupt list with durable
evidence behind it parks the reconcile instead of opening empty; clear/1
is the operator override. See guides/Persistence.md for the full model.
Summary
Types
The declared list with its version lineage, as persisted and shipped.
Functions
Adopts an incoming manifest when it wins: a higher version wholesale, a tie with differing content by lowest mutating node (WARN + tiebreak hook). MUST run inside the coordinator process.
Announces the local list version to hub peers; a lower-version peer pulls the full list. A no-op while the gate is off, parked, or nothing declared.
Applies a mutation as the leader; MUST run inside the coordinator process so writes serialize. A mutation that changes nothing does not bump the version.
Resolves the list on coordinator boot; see ProcessHub.Service.DeclaredChildren.Boot.
Operator call: clears the hub's declared list. Destructive — nothing remains declared and the reconcile stops running declared children. Written above every known version so it wins adoption everywhere; lifts the park state.
Returns the hub's declared children and the list version from local storage.
A hub without the feature in use returns %{version: 0, children: []}.
Parks continuation behind the write of manifest, the batch's working
copy. One :flush_declared is queued per batch (Batch.add/3) and lands
behind every request already in the coordinator's mailbox, so concurrent
durable commands share one write and sync — and none of them runs before
the write that covers its entry. MUST run inside the coordinator process.
Starts elector participation. Elector is node-global; the strategy module is set only when unset so another user of it keeps its configuration.
Writes and syncs the batch's working manifest once, publishes it (peers adopt the newest version, so the batch's intermediate versions need no broadcast of their own), then runs every parked command in arrival order and replies to its caller. A write that fails answers every parked caller with the error and runs none of them — no child starts on an intent that did not persist. MUST run inside the coordinator process; the coordinator flushes before it considers a peer's copy, so an adoption never overwrites a batch in flight.
The manifest wire/storage format this release reads and writes.
Resolves the hub's current leader: the elector leader when it is a hub member, otherwise the lexicographically lowest hub member — deterministic within a connected component, so exactly one node accepts writes.
Returns the cached manifest read directly from the hub's misc storage.
Handles a peer's version announce: when the local copy is older, fetches the peer's manifest in a supervised task and casts it back for adoption.
Builds a manifest at version with entries, mutated by this node.
Opens the list's durable store for the initializer; see Store.open/2.
Returns whether the hub's reconcile is parked over a lost declared list.
Commits the list additions a durable: true start requires, before any
process starts. Refuses when the gate is off, the list is parked, a spec is
:temporary, or no leader is reachable. :ok for non-durable starts.
Commits the list removals a stop requires, before any child terminates. The
leader's copy is authoritative; with no leader reachable the stop is refused
only when the local copy shows a declared child among child_ids.
Re-runs the boot-time remote comparison; MUST run inside the coordinator.
Returns the full cached manifest, or nil when none exists.
Types
@type manifest() :: %{ format: pos_integer(), version: non_neg_integer(), mutated_by: node(), entries: %{required(ProcessHub.child_id()) => ProcessHub.child_spec()} }
The declared list with its version lineage, as persisted and shipped.
Functions
@spec adopt(ProcessHub.Hub.t(), manifest()) :: :ok
Adopts an incoming manifest when it wins: a higher version wholesale, a tie with differing content by lowest mutating node (WARN + tiebreak hook). MUST run inside the coordinator process.
@spec announce_version(ProcessHub.Hub.t()) :: :ok
Announces the local list version to hub peers; a lower-version peer pulls the full list. A no-op while the gate is off, parked, or nothing declared.
@spec apply_mutation( ProcessHub.Hub.t(), {:add, [ProcessHub.child_spec()]} | {:remove, [term()]} ) :: :ok | {:pending, manifest()} | {:error, term()}
Applies a mutation as the leader; MUST run inside the coordinator process so writes serialize. A mutation that changes nothing does not bump the version.
A mutation that changes the list answers {:pending, manifest}: the new
manifest is the batch's working copy, not yet written. The coordinator parks
the command behind defer/4 and runs it from flush/1, after the one write
and sync that persists the whole batch — so N commands cost one manifest
write, not N rewrites of a list that grows with every child.
@spec boot(ProcessHub.Hub.t()) :: {:ok, :ready | :parked | {:remote_error, term()}} | {:error, term()}
Resolves the list on coordinator boot; see ProcessHub.Service.DeclaredChildren.Boot.
@spec clear(ProcessHub.hub_id()) :: :ok | {:error, term()}
Operator call: clears the hub's declared list. Destructive — nothing remains declared and the reconcile stops running declared children. Written above every known version so it wins adoption everywhere; lifts the park state.
@spec declared_children(ProcessHub.hub_id()) :: %{ version: non_neg_integer(), children: [ProcessHub.child_spec()] }
Returns the hub's declared children and the list version from local storage.
A hub without the feature in use returns %{version: 0, children: []}.
@spec defer(ProcessHub.Hub.t(), manifest(), GenServer.from(), (ProcessHub.Hub.t() -> {:reply, term(), ProcessHub.Hub.t()})) :: ProcessHub.Hub.t()
Parks continuation behind the write of manifest, the batch's working
copy. One :flush_declared is queued per batch (Batch.add/3) and lands
behind every request already in the coordinator's mailbox, so concurrent
durable commands share one write and sync — and none of them runs before
the write that covers its entry. MUST run inside the coordinator process.
@spec ensure_election() :: :ok
Starts elector participation. Elector is node-global; the strategy module is set only when unset so another user of it keeps its configuration.
@spec flush(ProcessHub.Hub.t()) :: ProcessHub.Hub.t()
Writes and syncs the batch's working manifest once, publishes it (peers adopt the newest version, so the batch's intermediate versions need no broadcast of their own), then runs every parked command in arrival order and replies to its caller. A write that fails answers every parked caller with the error and runs none of them — no child starts on an intent that did not persist. MUST run inside the coordinator process; the coordinator flushes before it considers a peer's copy, so an adoption never overwrites a batch in flight.
@spec format() :: pos_integer()
The manifest wire/storage format this release reads and writes.
@spec leader(ProcessHub.Hub.t()) :: node()
Resolves the hub's current leader: the elector leader when it is a hub member, otherwise the lexicographically lowest hub member — deterministic within a connected component, so exactly one node accepts writes.
@spec manifest(ProcessHub.Hub.t()) :: manifest() | nil
Returns the cached manifest read directly from the hub's misc storage.
@spec maybe_pull(ProcessHub.Hub.t(), node(), non_neg_integer()) :: :ok
Handles a peer's version announce: when the local copy is older, fetches the peer's manifest in a supervised task and casts it back for adoption.
@spec new_manifest(non_neg_integer(), %{ required(ProcessHub.child_id()) => ProcessHub.child_spec() }) :: manifest()
Builds a manifest at version with entries, mutated by this node.
@spec open_storage(ProcessHub.hub_id(), term()) :: %{ declared_backend: {module(), term()}, declared_path: String.t() }
Opens the list's durable store for the initializer; see Store.open/2.
@spec parked?(ProcessHub.Hub.t()) :: boolean()
Returns whether the hub's reconcile is parked over a lost declared list.
@spec precommit_start(ProcessHub.Hub.t(), [ProcessHub.child_spec()], keyword()) :: :ok | {:pending, manifest()} | {:error, term()}
Commits the list additions a durable: true start requires, before any
process starts. Refuses when the gate is off, the list is parked, a spec is
:temporary, or no leader is reachable. :ok for non-durable starts.
@spec precommit_stop(ProcessHub.Hub.t(), [ProcessHub.child_id()]) :: :ok | {:pending, manifest()} | {:error, term()}
Commits the list removals a stop requires, before any child terminates. The
leader's copy is authoritative; with no leader reachable the stop is refused
only when the local copy shows a declared child among child_ids.
@spec remote_recompare(ProcessHub.Hub.t()) :: :ok | {:error, term()}
Re-runs the boot-time remote comparison; MUST run inside the coordinator.
@spec snapshot(ProcessHub.hub_id()) :: manifest() | nil
Returns the full cached manifest, or nil when none exists.