portunus_registry (portunus v0.14.0)
View SourceA dynamic cluster-wide supervisor. "Dynamic" isn't an Erlang/OTP supervisor
type here: it means children are added and removed at runtime with add/3
and remove/2, unlike portunus_supervisor, whose children are the fixed
set returned once by init/1.
add/3 registers a child spec (a supervisor:child_spec(), or one carrying
an extended {permanent, Delay} restart option which portunus_delayed_restart
rewrites) under a key; portunus runs one election per key, and the elected
owner starts that child under a local Erlang/OTP supervisor.
remove/2 is the counterpart of add/3. A child is permanently removed
from the cluster once every node applies the change.
remove/2 on the owner alone moves the child to another node.
Restart is local and per-child, with one exception: a child that
crash-loops past the local supervisor's intensity takes the
registry down with {local_sup_down, _}. The restarted registry is
initially empty: the host re-adds its children, as it does after a node restart.
Summary
Functions
Register ChildSpec under its child id. Only one instance of it can run
in the cluster at any given time.
Register ChildSpec under Key. Only one instance of it can run in the
cluster at any given time. Validated
at registration: an invalid spec, a re-add with a different spec, or a
child id already used under another key is an error, not a later
elect-and-fail loop. Re-adding the identical spec is idempotent.
The keys this node is contending for.
Keys for which this node is currently the elected owner.
Stop and forget the child keyed by Key on this node. The key is only
gone cluster-wide once every node that added it calls remove/2; removing
it on the owner alone moves the child to another contender.
Like start_link/2, but registers the registry process under ServerName
(e.g. {local, shovel_registry}) so add/3, remove/2 and the rest can
address it by that name instead of a pid.
Make this node's registrations equal ChildSpecs, keyed by child id: specs
whose id is missing are added, registrations whose id is absent from the
set are removed (with remove/2's semantics: this node stops contending,
and ownership of a currently-owned key moves), and a changed spec is
applied as remove then add. An unchanged spec is untouched, so a second
sync/2 with the same set is a no-op and causes no ownership churn.
"Unchanged" compares identity keys when both sides have one (see
add/3), so specs with non-deterministic arguments avoid some churn.
Duplicate ids and invalid specs are refused before any change is applied.
If this node currently owns Key, hand ownership to TargetNode; if not,
return {error, not_owner}. Only the owner can transfer. If
TargetNode is not a ready contender the owner keeps running and the reply is
{error, {no_contender, TargetNode}}, never a handoff to some other node.
Move several owned keys to their targets in one batched command: the batch
equivalent of transfer/3.
The children this node currently runs (it was elected for), in
supervisor:which_children/1 shape [{Id, Child, Type, Modules}].
Types
-type registry_opts() :: #{ttl_ms => pos_integer(), sup_flags => supervisor:sup_flags(), affinity => portunus_affinity:spec(), group => term()}.
-type server() :: gen_server:server_ref().
Functions
-spec add(server(), portunus_delayed_restart:child_spec_in()) -> ok | {error, add_error()}.
Register ChildSpec under its child id. Only one instance of it can run
in the cluster at any given time.
-spec add(server(), term(), portunus_delayed_restart:child_spec_in()) -> ok | {error, add_error()}.
Register ChildSpec under Key. Only one instance of it can run in the
cluster at any given time. Validated
at registration: an invalid spec, a re-add with a different spec, or a
child id already used under another key is an error, not a later
elect-and-fail loop. Re-adding the identical spec is idempotent.
A spec whose args differ on every computation (encrypted credentials are
the common case) can carry an identity key: a term meaning "any two
specs with this value start the same child". When both sides carry one,
idempotence compares identities instead of specs. The key never reaches
a supervisor.
The keys this node is contending for.
Keys for which this node is currently the elected owner.
Stop and forget the child keyed by Key on this node. The key is only
gone cluster-wide once every node that added it calls remove/2; removing
it on the owner alone moves the child to another contender.
-spec start_link(portunus:name(), registry_opts()) -> {ok, pid()} | {error, term()}.
-spec start_link(gen_server:server_name(), portunus:name(), registry_opts()) -> {ok, pid()} | {error, term()}.
Like start_link/2, but registers the registry process under ServerName
(e.g. {local, shovel_registry}) so add/3, remove/2 and the rest can
address it by that name instead of a pid.
-spec stop(server()) -> ok.
-spec sync(server(), [portunus_delayed_restart:child_spec_in()]) -> ok | {error, add_error()}.
Make this node's registrations equal ChildSpecs, keyed by child id: specs
whose id is missing are added, registrations whose id is absent from the
set are removed (with remove/2's semantics: this node stops contending,
and ownership of a currently-owned key moves), and a changed spec is
applied as remove then add. An unchanged spec is untouched, so a second
sync/2 with the same set is a no-op and causes no ownership churn.
"Unchanged" compares identity keys when both sides have one (see
add/3), so specs with non-deterministic arguments avoid some churn.
Duplicate ids and invalid specs are refused before any change is applied.
Built for reconciling children from an external source of truth: an
add-only reconcile resurrects deleted children on a node that was offline
at delete time. sync/2 cannot fix a stale input; a node syncing from an
outdated view of the source still registers what that view says.
-spec transfer(server(), term(), node()) -> portunus:ok_or_error({no_contender, node()} | not_owner | no_quorum).
If this node currently owns Key, hand ownership to TargetNode; if not,
return {error, not_owner}. Only the owner can transfer. If
TargetNode is not a ready contender the owner keeps running and the reply is
{error, {no_contender, TargetNode}}, never a handoff to some other node.
Move several owned keys to their targets in one batched command: the batch
equivalent of transfer/3.
Each pair names a key and the node it should move to.
Keys on the list that are not owned by the caller are ignored (not moved).
Returns one {Key, ok | {error, term()}} per distinct key.
Validate a child spec with the same check add/3 and sync/2 apply,
returning the child id of a valid spec.
sync/2 refuses the entire set when one spec is invalid or two specs
share a child id, so a caller that wants to sync the valid specs and
report the rest validates each spec first with this function, using the
returned id to drop duplicates.
Any term is accepted. Anything but a valid
portunus_delayed_restart:child_spec_in/0 is an invalid_child_spec
error.
-spec which_children(server()) -> [{term(), pid() | restarting | undefined, worker | supervisor, [module()] | dynamic}].
The children this node currently runs (it was elected for), in
supervisor:which_children/1 shape [{Id, Child, Type, Modules}].