%% @doc The `ra_machine' behaviour.
%%
%% Used to implement the logic for the state machine running inside Ra.
%%
%% == Callbacks ==
%%
%% -callback init(Conf :: {@link machine_init_args()}) -> state()'
%%
%% Initialize a new machine state.
%%
%%
%% -callback apply(Meta :: command_meta_data(),
%% {@link command()}, effects(), State) ->
%% {State, {@link effects()}, {@link reply()}}
%%
%% Applies each entry to the state machine. Effects should be prepended to the
%% incoming list of effects. Ra will reverse the list of effects before
%% processing.
%%
%%
%% -callback state_enter(ra_server:ra_state() | eol, state()) -> effects().
%%
%%
%% Optional. Called when the ra server enters a new state. Called for all states
%% in the ra_server_proc gen_statem implementation not just for the standard
%% Raft states (follower, candidate, leader). If implemented it is sensible
%% to include a catch all clause as new states may be implemented in the future.
%%
%% -callback tick(TimeMs :: milliseconds(), state()) -> effects().
%%
%% Optional. Called periodically.
%% Suitable for issuing periodic side effects such as updating metrics systems.
%%
%%
%% -callback overview(state()) -> map().
%%
%% Optional. A map of overview information. Needs to be efficient.
-module(ra_machine).
-compile({no_auto_import, [apply/3]}).
-include("ra.hrl").
-export([init/2,
apply/5,
tick/3,
state_enter/3,
overview/2,
query/3,
module/1,
init_aux/2,
handle_aux/7
]).
-type state() :: term().
%% The state for a given machine implementation.
-type user_command() :: term().
%% the command type for a given machine implementation
-type machine_init_args() :: #{name := atom(), atom() => term()}.
%% the configuration passed to the init callback
-type machine() :: {machine, module(), AddInitArgs :: #{term() => term()}}.
%% Machine configuration.
%% the `module()' should implement the {@link ra_machine} behaviour.
-type milliseconds() :: non_neg_integer().
-type builtin_command() :: {down, pid(), term()} |
{nodeup | nodedown, node()}.
%% These commands may be passed to the {@link apply/2} function in reaction
%% to monitor effects
-type send_msg_opt() :: [ra_event | cast].
%% ra_event: the message will be wrapped up and sent as a ra event
%% e.g: `{ra_event, ra_server_id(), Msg}'
%%
%% cast: the message will be wrapped as a gen cast: ``{'$cast', Msg}''
-type send_msg_opts() :: send_msg_opt() | [send_msg_opt()].
-type locator() :: pid() | atom() | {atom(), node()}.
-type effect() ::
{send_msg, To :: locator(), Msg :: term()} |
{send_msg, To :: locator(), Msg :: term(), Options :: send_msg_opts()} |
{mod_call, module(), Function :: atom(), [term()]} |
{monitor, process, pid()} |
{monitor, node, node()} |
{demonitor, process, pid()} |
{demonitor, node, node()} |
{release_cursor, ra_index(), state()} |
{aux, term()} |
garbage_collection.
%% Effects are data structure that can be returned by {@link apply/4} to ask
%% ra to realise a side-effect in the real works, such as sending
%% a message to a process.
%%
%% Although both leaders and followers will process the same commands effects
%% are typically only applied on the leader. The only exceptions to this are
%% the `relaase_cursor' effect that is realised on all effects as it is part
%% of the ra implementation log truncation mechanism and the `garbage_collect'
%% effects that is used to explicitly triggering a garbage collection in the
%% ra servers's process.
%%
%% When leaders change and when clusters are restarted there is a small chance
%% that effects are issued multiple times so designing effects to be idempotent
%% is a good idea.
%%
%%
%%