partisan_broadcast_engine behaviour (partisan v6.0.0)

View Source

Behaviour for a broadcast group's tree engine.

A broadcast group (see partisan_plumtree_broadcast) delegates tree construction and repair to an engine implementing this behaviour. The group shell owns everything else — the process, mailbox, membership poll, handler dispatch, the rolling-upgrade router, ticks and transport — and the engine owns only the tree topology and the transitions that maintain it.

The engine is pure with respect to I/O: callbacks never send. They return an updated engine state together with a list of actions, which the shell executes via its transport in the returned order. This keeps the protocol testable and lets an alternate engine be swapped in behind the same interface.

Dispatch modes

An engine runs in one of two modes, which the shell reads from dispatch_mode/0:

  • Typed (the default) — the shell decodes each wire message and calls a Plumtree-shaped callback (handle_broadcast/8, handle_ihave/7, handle_graft/7, …), passing the handler's own verdicts (novelty, staleness). Plumtree is the first typed engine, partisan_plumtree_engine.
  • Raw — for an engine that runs its own wire protocol and de-duplication (for example partisan_thicket_engine). The shell hands it whole inbound messages via handle_message/2 and periodic ticks via repair_tick/1, and executes the raw_action/0 list it returns. Such a group hosts a single handler module, supplied by the shell, so raw actions carry no Mod.

A raw engine implements init/1, update_members/2, broadcast/4 and the query callbacks; it omits the typed handle_* / lazy_tick / select_exchange_peer callbacks. An engine that does not export dispatch_mode/0 runs in typed mode.

Summary

Types

A typed engine's action: transmit Msg on behalf of handler Mod to Peer (the peer's same-named group). The shell executes each in the order returned.

A raw engine's action. The group's single handler is supplied by the shell, so these carry no Mod

Opaque engine state; its internal shape is private to the engine module.

Callbacks

Query: the eager peers for the tree rooted at Root.

Query: the lazy peers for the tree rooted at Root.

Query: the engine's view of all known members.

Originates a broadcast from this node (the tree root is self): choose the eager and lazy targets to disseminate to.

Declares the engine's dispatch mode. Absent (not exported) means typed; a raw engine exports this returning raw.

Query (debug): the eager and lazy peers for the tree rooted at Root.

Handles a received broadcast (typed dispatch).

Handles a received graft (typed dispatch). GraftResult is the handler's graft/1 result for the requested message.

Handles an ignored_i_have — a peer's acknowledgement of a lazy push.

Handles a received lazy-push summary (i_have, typed dispatch). Stale is the handler's verdict on whether the advertised message is already held.

Handles one inbound wire message (raw dispatch), returning actions for the shell to execute.

Handles a received prune: demote the sender from eager to lazy.

Builds the initial engine state.

Periodic flush of outstanding lazy pushes, emitting i_have summaries.

Periodic repair and summary tick (raw dispatch).

Chooses a peer for a periodic anti-entropy exchange, or undefined to skip this round.

Reconciles the tree after membership changed to Members — attach joiners, drop leavers.

Types

action()

-type action() :: {send, node(), Msg :: term(), module()}.

A typed engine's action: transmit Msg on behalf of handler Mod to Peer (the peer's same-named group). The shell executes each in the order returned.

nodeset()

-type nodeset() :: ordsets:ordset(node()).

raw_action()

-type raw_action() ::
          {send, node(), Msg :: term()} |
          {deliver, MessageId :: term(), Payload :: term()} |
          {fetch, node(), MessageId :: term(), Root :: node(), Load :: term()}.

A raw engine's action. The group's single handler is supplied by the shell, so these carry no Mod:

  • send transmits a wire message to Peer;
  • deliver hands a received payload to the handler to store and apply;
  • fetch asks the shell to re-supply MessageId to Peer via the handler's graft/1, stamping the given piggyback load on the reply.

state()

-type state() :: term().

Opaque engine state; its internal shape is private to the engine module.

Callbacks

all_eager_peers/2

-callback all_eager_peers(Root :: node(), state()) -> nodeset().

Query: the eager peers for the tree rooted at Root.

all_lazy_peers/2

-callback all_lazy_peers(Root :: node(), state()) -> nodeset().

Query: the lazy peers for the tree rooted at Root.

all_members/1

-callback all_members(state()) -> nodeset().

Query: the engine's view of all known members.

broadcast/4

-callback broadcast(MessageId :: any(), Payload :: any(), Mod :: module(), state()) -> {state(), [action()]}.

Originates a broadcast from this node (the tree root is self): choose the eager and lazy targets to disseminate to.

dispatch_mode()

(optional)
-callback dispatch_mode() -> typed | raw.

Declares the engine's dispatch mode. Absent (not exported) means typed; a raw engine exports this returning raw.

get_peers/2

-callback get_peers(Root :: node(), state()) -> {nodeset(), nodeset()}.

Query (debug): the eager and lazy peers for the tree rooted at Root.

handle_broadcast/8

(optional)
-callback handle_broadcast(Novel :: boolean(),
                           MessageId :: any(),
                           Payload :: any(),
                           Mod :: module(),
                           Round :: non_neg_integer(),
                           Root :: node(),
                           From :: node(),
                           state()) ->
                              {state(), [action()]}.

Handles a received broadcast (typed dispatch).

Novel is the handler's verdict — new versus already seen — and it drives the eager-versus-prune decision, the one point of variation between tree protocols.

handle_graft/7

(optional)
-callback handle_graft(GraftResult :: stale | {ok, any()} | {error, any()},
                       MessageId :: any(),
                       Mod :: module(),
                       Round :: non_neg_integer(),
                       Root :: node(),
                       From :: node(),
                       state()) ->
                          {state(), [action()]}.

Handles a received graft (typed dispatch). GraftResult is the handler's graft/1 result for the requested message.

handle_ignored_ihave/6

(optional)
-callback handle_ignored_ihave(MessageId :: any(),
                               Mod :: module(),
                               Round :: non_neg_integer(),
                               Root :: node(),
                               From :: node(),
                               state()) ->
                                  {state(), [action()]}.

Handles an ignored_i_have — a peer's acknowledgement of a lazy push.

handle_ihave/7

(optional)
-callback handle_ihave(Stale :: boolean(),
                       MessageId :: any(),
                       Mod :: module(),
                       Round :: non_neg_integer(),
                       Root :: node(),
                       From :: node(),
                       state()) ->
                          {state(), [action()]}.

Handles a received lazy-push summary (i_have, typed dispatch). Stale is the handler's verdict on whether the advertised message is already held.

handle_message/2

(optional)
-callback handle_message(Msg :: term(), state()) -> {state(), [raw_action()]}.

Handles one inbound wire message (raw dispatch), returning actions for the shell to execute.

handle_prune/3

(optional)
-callback handle_prune(Root :: node(), From :: node(), state()) -> {state(), [action()]}.

Handles a received prune: demote the sender from eager to lazy.

init(Opts)

-callback init(Opts :: map()) -> state().

Builds the initial engine state.

Opts carries at least #{members := [node()]}. The engine may allocate private resources here (for example an ETS table for outstanding lazy pushes); they are owned by the calling group process.

lazy_tick/1

(optional)
-callback lazy_tick(state()) -> {state(), [action()]}.

Periodic flush of outstanding lazy pushes, emitting i_have summaries.

repair_tick/1

(optional)
-callback repair_tick(state()) -> {state(), [raw_action()]}.

Periodic repair and summary tick (raw dispatch).

select_exchange_peer/2

(optional)
-callback select_exchange_peer(Connected :: nodeset(), state()) -> node() | undefined.

Chooses a peer for a periodic anti-entropy exchange, or undefined to skip this round.

update_members/2

-callback update_members(Members :: nodeset(), state()) -> {state(), [action()]}.

Reconciles the tree after membership changed to Members — attach joiners, drop leavers.