%%%----------------------------------------------------------------------------- %% @doc Defines the base behaviour for election strategies. %% @end %%%----------------------------------------------------------------------------- -module(elector_strategy_behaviour). %%------------------------------------------------------------------------------ %% Callbacks %%------------------------------------------------------------------------------ -callback elect(CandidateNodes :: candidate_nodes()) -> Leader :: leader(). %%------------------------------------------------------------------------------ %% Exported API %%------------------------------------------------------------------------------ -export([elect/0, candidate_nodes/0, is_local_candidate/0]). %%------------------------------------------------------------------------------ %% Type definitions %%------------------------------------------------------------------------------ -type leader() :: node(). -type candidate_nodes() :: [node()]. %%------------------------------------------------------------------------------ %% Exported functions %%------------------------------------------------------------------------------ %% @doc Starts the election process by triggering the strategy %% modules elect() function. %% The election implementation should only contain the logic for %% selecting the leader node and returning the leader node name. %% The elect/0 function is triggered on all nodes automatically %% by the elector application. This means the strategy implementation %% does not have to worry about starting the election on all nodes. %% @end -spec elect() -> Leader :: leader(). elect() -> CandidateNodes = candidate_nodes(), erlang:apply( elector_config_handler:strategy_module(), elect, [CandidateNodes] ). %% @doc Returns the candidate nodes for the next election. %% %% Queries each connected node live (in parallel) for its current candidate %% status. The async candidate cache races with `candidate_status' %% propagation during cluster join — a node electing against an unpopulated %% cache self-elects and the wrong leader can stick — so we ask the source %% of truth on every election instead. %% @end -spec candidate_nodes() -> CandidateNodes :: candidate_nodes(). candidate_nodes() -> Nodes = [node() | nodes()], Results = erpc:multicall(Nodes, ?MODULE, is_local_candidate, [], 500), [Node || {Node, {ok, true}} <- lists:zip(Nodes, Results)]. %% @doc Returns whether the local node is currently a candidate. Exported so %% remote nodes can query it via erpc from `candidate_nodes/0'. %% @end -spec is_local_candidate() -> boolean(). is_local_candidate() -> case erlang:whereis(elector_candidate) of undefined -> false; Pid -> try gen_server:call(Pid, is_candidate_node, 300) of {ok, true} -> true; _ -> false catch _:_ -> false end end.