portunus_registry (portunus v0.8.0)

View Source

A 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 stops the local election; the child is gone cluster-wide once every node that added it calls remove/2, and a remove/2 on the owner alone moves the child to another node.

No new replicated state: the registry holds only local election pids and a local supervisor. 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, _}, and the restarted registry is empty: the host re-adds its children, as it does after a node restart.

The registry's cleanup releases cluster-wide locks, so a child spec supervising it should give it a generous shutdown value.

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.

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.

The children this node currently runs (it was elected for), in supervisor:which_children/1 shape [{Id, Child, Type, Modules}].

Types

add_error()

-type add_error() ::
          {invalid_child_spec, term()} | {already_added, term()} | {duplicate_child_id, term()}.

registry_opts()

-type registry_opts() ::
          #{ttl_ms => pos_integer(),
            sup_flags => supervisor:sup_flags(),
            affinity => portunus_affinity:spec(),
            group => term()}.

server()

-type server() :: gen_server:server_ref().

Functions

add(Server, ChildSpec)

-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.

add(Server, Key, ChildSpec)

-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.

handle_call/3

handle_cast(Msg, State)

handle_info/2

init/1

keys(Server)

-spec keys(server()) -> [term()].

The keys this node is contending for.

owned_keys(Server)

-spec owned_keys(server()) -> [term()].

Keys for which this node is currently the elected owner.

remove(Server, Key)

-spec remove(server(), term()) -> ok.

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.

start_link(Name, Opts)

-spec start_link(portunus:name(), registry_opts()) -> {ok, pid()} | {error, term()}.

start_link(ServerName, Name, Opts)

-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.

stop(Server)

-spec stop(server()) -> ok.

terminate/2

transfer(Server, Key, TargetNode)

-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.

which_children(Server)

-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}].