portunus_election behaviour (portunus v0.8.0)

View Source

Helps implement a leader election with application-specific semantics.

A candidate (an election participant) runs on every node. At most one candidate is elected at a time (at any committed Raft index).

When a candidate is elected, the elected/1 callback is called. Its opposite, stepped_down/1, is invoked when the participant loses leadership.

-callback elected(Ctx :: election_ctx()) -> {ok, State :: term()}.
-callback stepped_down(State :: term()) -> ok.

Ctx is an election_ctx() map carrying name, key, token, and args, so the elected leader can use the fencing token for operations on external resources.

Summary

Functions

Whether this participant is the elected owner. An election blocked in a Ra command (a quorum loss, a slow elected/1) is not the owner, so a caller that must not block treats a timeout as false.

Stop several elections concurrently against one deadline, killing stragglers. Each election's terminate runs user stepped_down code plus a revoke that blocks up to the command timeout under no quorum, so a serial stop holds the caller for the sum. A killed election's revoke is lost; TTL expiry covers it.

Ask this node's election, if it is the current owner of its key, to hand ownership to TargetNode. It pre-checks that TargetNode is a ready contender, stops the local work, issues the token-fenced transfer, and on success re-contends as a standby; if the target was not ready it restores the local work and stays owner. Returns {error, not_owner} when this node is not the owner, and {error, {no_contender, TargetNode}} when the target is not a ready contender. {error, no_quorum} means the command timed out and its outcome is unknown: the work stays stopped while the election settles ownership itself (restoring it or re-contending), so the caller retries later rather than treating it as a failed transfer. A retry made before that settles also returns {error, not_owner}; it does not prove ownership moved.

Types

election_ctx()

-type election_ctx() ::
          #{name := portunus:name(),
            key := portunus:lock_key(),
            token := portunus:token(),
            args := term()}.

election_opts()

-type election_opts() :: #{ttl_ms => pos_integer(), affinity => portunus_affinity:spec()}.

Callbacks

elected/1

-callback elected(election_ctx()) -> {ok, State :: term()}.

stepped_down(State)

-callback stepped_down(State :: term()) -> ok.

Functions

handle_call/3

handle_cast(Msg, State)

handle_info/2

init/1

is_leader(Pid)

-spec is_leader(pid()) -> boolean().

is_leader(Pid, Timeout)

-spec is_leader(pid(), timeout()) -> boolean().

Whether this participant is the elected owner. An election blocked in a Ra command (a quorum loss, a slow elected/1) is not the owner, so a caller that must not block treats a timeout as false.

start_link(Name, Key, Mod, Args)

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

start_link(Name, Key, Mod, Args, Opts)

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

stop(Pid)

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

stop_all(Pids)

-spec stop_all([pid()]) -> ok.

Stop several elections concurrently against one deadline, killing stragglers. Each election's terminate runs user stepped_down code plus a revoke that blocks up to the command timeout under no quorum, so a serial stop holds the caller for the sum. A killed election's revoke is lost; TTL expiry covers it.

stop_all(Pids, TimeoutMs)

-spec stop_all([pid()], pos_integer()) -> ok.

terminate/2

transfer_to(Pid, TargetNode)

-spec transfer_to(pid(), node()) -> portunus:ok_or_error({no_contender, node()} | not_owner | no_quorum).

Ask this node's election, if it is the current owner of its key, to hand ownership to TargetNode. It pre-checks that TargetNode is a ready contender, stops the local work, issues the token-fenced transfer, and on success re-contends as a standby; if the target was not ready it restores the local work and stays owner. Returns {error, not_owner} when this node is not the owner, and {error, {no_contender, TargetNode}} when the target is not a ready contender. {error, no_quorum} means the command timed out and its outcome is unknown: the work stays stopped while the election settles ownership itself (restoring it or re-contending), so the caller retries later rather than treating it as a failed transfer. A retry made before that settles also returns {error, not_owner}; it does not prove ownership moved.