Leader election behavior.
Behaviours: gen_server
.
This module defines the locks_leader
behaviour.
Required callback functions: init/1
, elected/3
, surrendered/3
, handle_DOWN/3
, handle_leader_call/4
, handle_leader_cast/3
, from_leader/3
, handle_call/4
, handle_cast/3
, handle_info/3
.
This behavior is inspired by gen_leader, and offers the same API
except for a few details. The leader election strategy is based on
the locks
library. The leader election group is identified by the
lock used - by default, [locks_leader, CallbackModule]
, but configurable
using the option {resource, Resource}
, in which case the lock name will
be [locks_leader, Resource]
. The lock corresponding to the leader group
will in the following description be referred to as The Lock.
Each instance is started either as a 'candidate' or a 'worker'.
Candidates all try to claim a write lock on The Lock, and workers merely
monitor it. All candidates and workers will find each other through the
#locks_info{}
messages. This means that, unlike gen_leader, the
locks_leader dynamically adopts new nodes, candidates and workers. It is
also possible to have multiple candidates and workers on the same node.
The candidate that is able to claim The Lock becomes the leader.
Leader instance:
Mod:elected(ModState, Info, undefined) -> {ok, Sync, ModState1}.
Other instances:
Mod:surrendered(ModState, Sync, Info) -> {ok, ModState1}.
If a candidate or worker joins the group, the same function is called,
but with the Pid of the new member as third argument. It can then
return either {reply, Sync, ModState1}
, in which case only the new
member will get the Sync message, or {ok, Sync, ModState1}
, in which case
all group members will be notified.
The locks_leader
behavior will automatically heal from netsplits and
ensure that there is only one leader. A candidate that was the leader but
is forced to surrender, can detect this e.g. by noting in its own state
when it becomes leader:
surrendered(#state{is_leader = true} = S, Sync, _Info) ->
%% I was leader; a netsplit has occurred
{ok, surrendered_after_netsplit(S, Sync, Info)};
surrendered(S, Sync, _Info) ->
{ok, normal_surrender(S, Sync, Info)}.
The newly elected candidate normally doesn't know that a split-brain
has occurred, but can sync with other candidates using e.g. the function
ask_candidates/2
, which functions rather like a parallel
gen_server:call/2
.
abstract datatype: election()
ldr_options() = [option()]
abstract datatype: leader_info()
mod_state() = any()
msg() = any()
option() = {role, candidate | worker} | {resource, any()}
server_ref() = atom() | pid()
ask_candidates/2 | Send a synchronous request to all candidates. |
broadcast/2 | Broadcast Msg to all candidates and workers. |
broadcast_to_candidates/2 | Broadcast Msg to all (synced) candidates. |
call/2 | Make a gen_server -like call to the leader candidate L . |
call/3 | Make a timeout-guarded gen_server -like call to the leader
candidate L . |
candidates/1 | Return the current list of candidates. |
cast/2 | Make a gen_server -like cast to the leader candidate L . |
info/1 | |
info/2 | |
leader/1 | Return the leader pid, or undefined if there is no current leader. |
leader_call/2 | Make a synchronous call to the leader. |
leader_call/3 | Make a timeout-guarded synchronous call to the leader. |
leader_cast/2 | Make an asynchronous cast to the leader. |
leader_node/1 | Return the node of the current leader. |
leader_reply/2 | |
new_candidates/1 | Return the current list of candidates that have not yet been synced. |
record_fields/1 | |
reply/2 | Corresponds to gen_server:reply/2 . |
start_link/2 | Starts an anonymous locks_leader candidate using Module as callback. |
start_link/3 | Starts an anonymous worker or candidate. |
start_link/4 | Starts a locally registered worker or candidate. |
workers/1 | Return the current list of workers. |
ask_candidates(Req::any(), St::election()) -> {GoodReplies, Errors}
GoodReplies = [{pid(), any()}]
Errors = [{pid(), any()}]
Send a synchronous request to all candidates.
The request Req
will be processed in Mod:handle_call/4
and can be
handled as any other request. The return value separates the good replies
from the failed (the candidate died or couldn't be reached).
broadcast(Msg::any(), St::election()) -> ok
Broadcast Msg
to all candidates and workers.
This function may only be called from the current leader.
The message will be processed in the Mod:from_leader/3
callback.
Note: You should not use this function from the Mod:elected/3
function,
since it may cause sequencing issues with the broadcast message that is
(normally) sent once the Mod:elected/3
function returns.
broadcast_to_candidates(Msg::any(), St::election()) -> ok
Broadcast Msg
to all (synced) candidates.
This function may only be called from the current leader.
The message will be processed in the Mod:from_leader/3
callback.
Note: You should not use this function from the Mod:elected/3
function,
since it may cause sequencing issues with the broadcast message that is
(normally) sent once the Mod:elected/3
function returns.
call(L::server_ref(), Request::any()) -> any()
Make a gen_server
-like call to the leader candidate L
.
call(L::server_ref(), Request::any(), Timeout::integer() | infinity) -> any()
Make a timeout-guarded gen_server
-like call to the leader
candidate L
.
candidates(St::election()) -> [pid()]
Return the current list of candidates.
cast(L::server_ref(), Msg::any()) -> ok
Make a gen_server
-like cast to the leader candidate L
.
info(L) -> any()
info(L, Item) -> any()
leader(St::election()) -> pid() | undefined
Return the leader pid, or undefined
if there is no current leader.
leader_call(Name::server_ref(), Request::term()) -> term()
Make a synchronous call to the leader.
This function is similar to gen_server:call/2
, but is forwarded to
the leader by the leader candidate L
(unless, of course, it is the
leader, in which case it handles it directly). If the leader should die
before responding, this function will raise an error({leader_died,...})
exception.
leader_call(Name::server_ref(), Request::term(), Timeout::integer() | infinity) -> term()
Make a timeout-guarded synchronous call to the leader.
This function is similar to gen_server:call/3
, but is forwarded to
the leader by the leader candidate L
(unless, of course, it is the
leader, in which case it handles it directly). If the leader should die
before responding, this function will raise an error({leader_died,...})
exception.
leader_cast(L::server_ref(), Msg::term()) -> ok
Make an asynchronous cast to the leader.
This function is similar to gen_server:cast/2
, but is forwarded to
the leader by the leader candidate L
(unless, of course, it is the
leader, in which case it handles it directly). No guarantee is given
that the cast actually reaches the leader (i.e. if the leader dies, no
attempt is made to resend to the next elected leader).
leader_node(St::election()) -> node()
Return the node of the current leader.
This function is mainly present for compatibility with gen_leader
.
leader_reply(From, Reply) -> any()
new_candidates(St::election()) -> [pid()]
Return the current list of candidates that have not yet been synced.
This function is mainly indented to be used from within Mod:elected/3
,
once a leader has been elected. One possible use is to contact the
new candidates to see whether one of them was a leader, which could
be the case if the candidates appeared after a healed netsplit.
record_fields(X1) -> any()
reply(From::{pid(), any()}, Reply::any()) -> ok
Corresponds to gen_server:reply/2
.
Callback modules should use this function instead in order to be future safe.
start_link(Module::atom(), St::any()) -> {ok, pid()}
Starts an anonymous locks_leader candidate using Module
as callback.
The leader candidate will sync with all candidates using the same callback module, on all connected nodes.
start_link(Module::atom(), St::any(), Options::ldr_options()) -> {ok, pid()}
Starts an anonymous worker or candidate.
The following options are supported:
-
{role, candidate | worker}
- A candidate is able to take on the leader role, if elected; a worker simply follows the elections and receives broadcasts from the leader. -
{resource, Resource}
- The name of the lock used for the election is normally[locks_leader, Module]
, but with this option, it can be changed into[locks_leader, Resource]
. Note that, under the rules of the locks application, a lock name must be a list.
start_link(Reg::atom(), Module::atom(), St::any(), Options::ldr_options()) -> {ok, pid()}
Starts a locally registered worker or candidate.
Note that only one registered instance of the same name (using the built-in process registry) can exist on a given node. However, it is still possible to have multiple instances of the same election group on the same node, either anonymous, or registered under different names.
For a description of the options, see start_link/3
.
workers(St::election()) -> [pid()]
Return the current list of workers.