Skip to content

Latest commit

 

History

History
537 lines (274 loc) · 13.9 KB

locks_leader.md

File metadata and controls

537 lines (274 loc) · 13.9 KB

Module locks_leader

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.

Description

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.

Split brain

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.

Data Types

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()

Function Index

ask_candidates/2Send a synchronous request to all candidates.
broadcast/2Broadcast Msg to all candidates and workers.
broadcast_to_candidates/2Broadcast Msg to all (synced) candidates.
call/2Make a gen_server-like call to the leader candidate L.
call/3Make a timeout-guarded gen_server-like call to the leader candidate L.
candidates/1Return the current list of candidates.
cast/2Make a gen_server-like cast to the leader candidate L.
info/1
info/2
leader/1Return the leader pid, or undefined if there is no current leader.
leader_call/2Make a synchronous call to the leader.
leader_call/3Make a timeout-guarded synchronous call to the leader.
leader_cast/2Make an asynchronous cast to the leader.
leader_node/1Return the node of the current leader.
leader_reply/2
new_candidates/1Return the current list of candidates that have not yet been synced.
record_fields/1
reply/2Corresponds to gen_server:reply/2.
start_link/2Starts an anonymous locks_leader candidate using Module as callback.
start_link/3Starts an anonymous worker or candidate.
start_link/4Starts a locally registered worker or candidate.
workers/1Return the current list of workers.

Function Details

ask_candidates/2


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/2


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/2


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/2


call(L::server_ref(), Request::any()) -> any()



Make a gen_server-like call to the leader candidate L.

call/3


call(L::server_ref(), Request::any(), Timeout::integer() | infinity) -> any()



Make a timeout-guarded gen_server-like call to the leader candidate L.

candidates/1


candidates(St::election()) -> [pid()]



Return the current list of candidates.

cast/2


cast(L::server_ref(), Msg::any()) -> ok



Make a gen_server-like cast to the leader candidate L.

info/1

info(L) -> any()

info/2

info(L, Item) -> any()

leader/1


leader(St::election()) -> pid() | undefined



Return the leader pid, or undefined if there is no current leader.

leader_call/2


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/3


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/2


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/1


leader_node(St::election()) -> node()



Return the node of the current leader.

This function is mainly present for compatibility with gen_leader.

leader_reply/2

leader_reply(From, Reply) -> any()

new_candidates/1


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/1

record_fields(X1) -> any()

reply/2


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/2


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/3


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/4


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/1


workers(St::election()) -> [pid()]



Return the current list of workers.