portunus_joiner (portunus v0.14.0)

View Source

Runs the cluster join and convergence loop around portunus:join_or_form/3: one process per cluster, supervised by the consumer.

While joining, each pass runs the ensure_system fun and then join_or_form/3, retrying with backoff. Once joined, each pass only checks membership and rejoins when the check fails: portunus:is_member/1 catches a dead or reset local server, portunus:is_seed_cluster_member/2 catches a node the seed's cluster dropped.

The subscriber (by default the process that called start_link/1) receives {portunus_joiner, Name, joined} and {portunus_joiner, Name, rejoining}. These are status reports, not edges: a restarted joiner reports joined again with no rejoining in between.

Wait for joined before registering children, and do not stop anything on rejoining: it is often a false alarm, and leases protect running children.

Removing departed members stays with the consumer: only the host knows that a node is gone for good. See portunus:remove_member/2.

Summary

Functions

The decision core: an event and the current state in, the actions and the next state out. It is pure, so the unit and property suites drive it without a cluster. The gen_server runs the passes and performs the actions.

Re-evaluate membership now. Call this from the host's membership event handler. It is asynchronous, and a burst of calls runs one pass.

Start a joiner for cluster name on Ra system system, linked to the caller.

Types

action()

-type action() :: {notify, joined | rejoining} | {arm, non_neg_integer()}.

decide_state()

-type decide_state() ::
          #{status := status(), backoff_ms := pos_integer(), recheck_interval_ms := pos_integer()}.

event()

-type event() :: trigger | pass_ok | {pass_failed, term()}.

opts()

-type opts() ::
          #{system := portunus:system(),
            name := portunus:name(),
            candidates := fun(() -> [node()]),
            ensure_system => fun(() -> portunus:ok_or_error(term())),
            subscriber => pid(),
            recheck_interval_ms => pos_integer()}.

status()

-type status() :: joining | member.

Functions

decide/2

-spec decide(event(), decide_state()) -> {[action()], decide_state()}.

The decision core: an event and the current state in, the actions and the next state out. It is pure, so the unit and property suites drive it without a cluster. The gen_server runs the passes and performs the actions.

A trigger re-arms the single timer to fire immediately, so bursts coalesce into one pass. A failed pass while joining doubles the backoff. A failed membership check reports rejoining and rejoins on the next, immediately armed pass.

handle_call(Req, From, State)

handle_cast/2

handle_info/2

init/1

recheck(Pid)

-spec recheck(pid()) -> ok.

Re-evaluate membership now. Call this from the host's membership event handler. It is asynchronous, and a burst of calls runs one pass.

start_link/1

-spec start_link(opts()) -> {ok, pid()} | {error, term()}.

Start a joiner for cluster name on Ra system system, linked to the caller.

candidates returns the nodes that should form one cluster, typically the host's membership view. node() is added and the list sorted. A raising fun counts as a failed attempt and is retried.

ensure_system runs before every join attempt and defaults to a fun returning ok. A tenant of a host-owned system passes fun() -> portunus:use_system(System) end. The fun is retried with the join because it can fail early in the host's boot.

recheck_interval_ms (default 60000) paces the periodic membership re-check.

terminate(Reason, State)