Packages

Elects master node from Erlang/Elixir cluster that is agreed by all nodes.

Current section

Files

Jump to
elector src elector_commission.erl
Raw

src/elector_commission.erl

%%%-----------------------------------------------------------------------------
%% @doc Main elector state process who's responsibility is to start the
%% election process on start up either syncronously or asyncronously.
%% Also sets up monitoring for node up and down events and triggers
%% automatic election if a node goes down or comes up.
%% @private
%% @end
%%%-----------------------------------------------------------------------------
-module(elector_commission).
%%------------------------------------------------------------------------------
%% Behaviours
%%------------------------------------------------------------------------------
-behaviour(gen_server).
%%------------------------------------------------------------------------------
%% Exported API
%%------------------------------------------------------------------------------
-export([start_link/0, start/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2]).
%%------------------------------------------------------------------------------
%% Types
%%------------------------------------------------------------------------------
-type election_schedule_ref() :: undefined | reference().
-type state() ::
#{schedule_ref => election_schedule_ref(), leader_node => node() | undefined}.
%%------------------------------------------------------------------------------
%% Exported functions
%%------------------------------------------------------------------------------
%% @doc Starts the elector state process.
-spec start_link() -> {ok, pid()} | ignore | {error, term()}.
start_link() ->
case gen_server:start_link({global, ?MODULE}, ?MODULE, #{}, []) of
{ok, Pid} ->
{ok, Pid};
{error, {already_started, Pid}} ->
{ok, Pid}
end.
start() ->
case gen_server:start({global, ?MODULE}, ?MODULE, #{}, []) of
{ok, Pid} ->
{ok, Pid};
{error, {already_started, Pid}} ->
{ok, Pid}
end.
%%------------------------------------------------------------------------------
%% Callback functions
%%------------------------------------------------------------------------------
-spec init(any()) -> {ok, state()}.
init(_) ->
net_kernel:monitor_nodes(true),
SheduleRef = schedule_election(undefined, 0),
{ok, #{schedule_ref => SheduleRef, leader_node => undefined}}.
handle_info(election_schedule, State) ->
{noreply, start_election(State)};
handle_info({nodeup, _Node}, #{schedule_ref := Sr} = State) ->
NewState = maps:put(schedule_ref, schedule_election(Sr, undefined), State),
{noreply, NewState};
handle_info({nodedown, _Node}, #{schedule_ref := Sr} = State) ->
NewState = maps:put(schedule_ref, schedule_election(Sr, undefined), State),
{noreply, NewState};
handle_info(_Msg, State) ->
{noreply, State}.
handle_call(get_leader, _From, State) ->
{reply, maps:get(leader_node, State), State};
handle_call(start_election, _From, State) ->
#{leader_node := LeaderNode} = NewState = start_election(State),
{reply, LeaderNode, NewState};
handle_call(Msg, _From, State) ->
{reply, Msg, State}.
handle_cast(start_election, State) ->
{noreply, start_election(State)};
handle_cast(_msg, State) ->
{noreply, State}.
%%------------------------------------------------------------------------------
%% Internal functions
%%------------------------------------------------------------------------------
%% @private
start_election(State) ->
LeaderNode = elector_service:setup_election(#{run_hooks => true}),
StateLeader = maps:put(leader_node, LeaderNode, State),
maps:put(schedule_ref, maps:put(schedule_ref, undefined, State), StateLeader).
%% @private
schedule_election(ScheduleRef, _Delay) when is_reference(ScheduleRef) ->
AutomaticElection = elector_config_handler:automatic_elections(),
case AutomaticElection of
true ->
ScheduleRef;
false ->
undefined
end;
schedule_election(_ScheduleRef, Delay) ->
QuorumCheck = elector_config_handler:quorum_check(),
AutomaticElection = elector_config_handler:automatic_elections(),
case QuorumCheck andalso AutomaticElection of
true ->
send_election_msg(Delay);
false ->
undefined
end.
%% @private
send_election_msg(Delay) ->
AgreedDelay =
case is_number(Delay) of
true ->
Delay;
false ->
elector_config_handler:election_delay()
end,
erlang:send_after(AgreedDelay, self(), election_schedule).