partisan_plumtree_broadcast_handler behaviour (partisan v6.0.0)

View Source

Behaviour for a partisan_plumtree_broadcast handler: the application-specific half of an epidemic broadcast.

A handler defines how a broadcast payload is produced, de-duplicated, applied, recovered and reconciled. Each handler runs in its own broadcast group — its own spanning tree and process — and partisan_broadcast:broadcast/2 routes to the group by handler module.

Delivery paths

A handler chooses one of two apply paths:

  • Synchronous — implement merge/2. The broadcast process both de-duplicates and applies the payload, and waits for the apply to finish.
  • Non-blocking — implement claim/2 and handle_broadcast/2. claim/2 runs on the broadcast process and does only the fast, atomic novelty check; the heavy apply runs off the tree in the handler's own process, so a slow apply never blocks the tree or the other handlers. When claim/2 is exported it is used in place of merge/2.

broadcast_channel/0, claim/2 and handle_broadcast/2 are optional; the rest are required.

Summary

Callbacks

Returns the channel on which this handler's broadcasts are sent.

Returns the {MessageId, Payload} to disseminate for a given application broadcast.

Atomically decides whether MessageId is novel and records it as seen — the fast half of the non-blocking delivery path (ADR-000001).

Triggers an anti-entropy exchange between this handler and the one on Node.

Re-supplies the payload for a previously advertised message id.

Applies a previously claimed broadcast payload — the heavy half of the non-blocking delivery path.

Returns true if the message with the given id has already been received.

De-duplicates and applies a received broadcast, synchronously.

Callbacks

broadcast_channel()

(optional)
-callback broadcast_channel() -> partisan:channel().

Returns the channel on which this handler's broadcasts are sent.

Optional; defaults to the default channel. See partisan_plumtree_broadcast:broadcast/2.

broadcast_data/1

-callback broadcast_data(any()) -> {MessageId :: any(), Payload :: any()}.

Returns the {MessageId, Payload} to disseminate for a given application broadcast.

MessageId identifies the message for de-duplication and for lazy-push (i_have) advertisement; Payload is what peers apply.

claim(MessageId, Payload)

(optional)
-callback claim(MessageId :: any(), Payload :: any()) -> boolean().

Atomically decides whether MessageId is novel and records it as seen — the fast half of the non-blocking delivery path (ADR-000001).

Returns true if newly claimed, in which case the caller applies the payload via handle_broadcast/2, or false if already seen. Runs ON the broadcast process, so it must be fast and confine its side effects to the seen-set (for example ets:insert_new/2 on the handler's own table). Because the id is recorded before the asynchronous apply runs, a redelivery is still recognised as a duplicate, so at-least-once idempotent delivery is preserved.

exchange/1

-callback exchange(node()) -> ok | {ok, pid()} | {error, term()} | ignore.

Triggers an anti-entropy exchange between this handler and the one on Node.

The exchange reconciles messages missing on either side. It should run as a background process and need not account for messages in flight when it starts, or broadcast during its operation — a later exchange covers those. Return ignore to decline.

graft/1

-callback graft(any()) -> stale | {ok, any()} | {error, any()}.

Re-supplies the payload for a previously advertised message id.

Returns {ok, Payload} for the requested message, stale if a later message has already subsumed it, or {error, Reason}. The broadcast process calls this to answer a graft from a peer repairing its tree.

handle_broadcast(MessageId, Payload)

(optional)
-callback handle_broadcast(MessageId :: any(), Payload :: any()) -> ok.

Applies a previously claimed broadcast payload — the heavy half of the non-blocking delivery path.

Runs in the handler's own process: the broadcast server delivers the payload as a gen_server cast, {'$partisan_apply', MessageId, Payload}, and never waits on it, so the apply may do heavy work (CRDT merge, persistence) without blocking the broadcast tree or the other handlers.

is_stale/1

-callback is_stale(any()) -> boolean().

Returns true if the message with the given id has already been received.

The broadcast process calls this on a lazy-push (i_have) advertisement to decide whether to request the message.

merge/2

-callback merge(any(), any()) -> boolean().

De-duplicates and applies a received broadcast, synchronously.

Given the message id and payload, applies the message to local state and returns true if it was novel or false if it had already been received. Runs on the broadcast process, which waits for it — prefer the non-blocking claim/2 + handle_broadcast/2 path when the apply is expensive.