Packages

macula

0.7.13
7.1.0 7.0.0 6.0.0 5.2.2 5.2.1 5.2.0 5.1.0 5.0.0 4.8.0 4.7.1 4.7.0 4.6.0 4.5.0 4.4.10 4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.1 4.3.0 4.2.9 4.2.8 4.2.7 4.2.6 4.2.5 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.1 4.1.0 4.0.0 3.16.0 3.15.3 3.15.2 3.15.1 3.14.0 3.13.0 3.12.1 3.12.0 3.11.1 3.11.0 3.10.3 3.10.2 3.10.1 3.9.0 3.8.0 3.7.0 3.5.0 3.4.0 3.3.0 3.2.0 3.1.0 3.0.0 2.1.1 2.1.0 2.0.0 1.5.2 1.5.1 1.4.30 1.4.29 1.4.28 1.4.27 1.4.26 1.4.25 1.4.24 1.4.23 1.4.22 1.4.21 1.4.20 1.4.19 1.4.18 1.4.17 1.4.16 1.4.15 1.4.14 1.4.13 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.1 1.3.0 1.2.0 1.1.0 1.0.10 1.0.9 1.0.8 1.0.7 1.0.6 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 1.0.0 0.48.6 0.48.5 0.48.4 0.48.3 0.48.2 0.48.1 0.48.0 0.47.1 0.47.0 0.46.3 0.46.1 0.46.0 0.45.3 0.45.2 0.45.1 0.45.0 0.44.2 0.44.1 0.44.0 0.43.3 0.43.2 0.43.1 0.43.0 0.42.9 0.42.8 0.42.7 0.42.6 0.42.5 0.42.4 0.42.3 0.42.2 0.42.1 0.42.0 0.41.1 0.41.0 0.40.1 0.40.0 0.39.9 0.39.8 0.39.7 0.39.6 0.39.5 0.39.4 0.39.3 0.39.2 0.39.1 0.39.0 0.38.8 0.38.7 0.38.6 0.38.5 0.38.4 0.38.3 0.38.2 0.38.1 0.38.0 0.37.7 0.37.6 0.37.5 0.37.4 0.37.3 0.37.2 0.37.1 0.37.0 0.36.6 0.36.5 0.36.4 0.36.3 0.36.2 0.36.1 0.36.0 0.35.4 0.35.3 0.35.2 0.35.1 0.35.0 0.34.1 0.34.0 0.33.1 0.33.0 0.32.5 0.32.4 0.32.3 0.32.2 0.32.1 0.32.0 0.31.9 0.31.8 0.31.7 0.31.6 0.31.5 0.31.4 0.31.3 0.31.2 0.31.1 0.31.0 0.30.10 0.30.9 0.30.8 0.30.7 0.30.6 0.30.5 0.30.4 0.30.3 0.30.2 0.30.1 0.30.0 0.29.0 0.28.3 0.28.2 0.28.1 0.28.0 0.27.1 0.27.0 0.26.1 0.26.0 0.25.6 0.25.5 0.25.4 0.25.3 0.25.2 0.25.1 0.25.0 0.24.6 0.24.5 0.24.4 0.24.3 0.24.2 0.24.1 0.24.0 0.23.3 0.23.2 0.23.1 0.23.0 0.22.12 0.22.11 0.22.10 0.22.9 0.22.8 0.22.7 0.22.6 0.22.5 0.22.4 0.22.3 0.22.2 0.22.1 0.22.0 0.21.7 0.21.6 0.21.5 0.21.4 0.21.2 0.21.1 0.21.0 0.20.25 0.20.24 0.20.23 0.20.22 0.20.21 0.20.20 0.20.19 0.20.18 0.20.17 0.20.16 0.20.15 0.20.14 0.20.13 0.20.12 0.20.11 0.20.10 0.20.9 0.20.8 0.20.7 0.20.6 0.20.5 0.20.3 0.20.2 0.20.1 0.20.0 0.19.2 0.19.1 0.19.0 0.18.1 0.18.0 0.17.4 0.17.3 0.17.2 0.17.1 0.17.0 0.16.6 0.16.5 0.16.4 0.16.3 0.16.2 0.16.1 0.16.0 0.15.1 0.15.0 0.14.3 0.14.2 0.14.1 0.14.0 0.12.6 0.12.5 0.12.3 0.11.3 0.10.2 0.10.1 0.10.0 0.9.2 0.9.1 0.9.0 0.8.25 0.8.24 0.8.23 0.8.22 0.8.21 0.8.20 0.8.19 0.8.18 0.8.17 0.8.16 0.8.15 0.8.14 0.8.13 0.8.12 0.8.11 0.8.10 0.8.9 0.8.8 0.8.7 0.8.6 0.8.5 0.8.4 0.8.3 0.8.2 0.8.1 0.8.0 0.7.30 0.7.29 0.7.28 0.7.27 0.7.26 0.7.25 0.7.24 0.7.23 0.7.22 0.7.21 0.7.20 0.7.19 0.7.18 0.7.17 0.7.16 0.7.15 0.7.14 0.7.13 0.7.12 0.7.11 0.7.10 0.7.9 0.7.8 0.7.7 0.7.6 0.7.5 0.7.4 0.7.3 0.7.2 0.7.1 0.7.0 0.6.7 0.6.6 0.6.5 0.6.4 0.6.3 0.6.2 0.6.1 0.6.0 0.5.0 0.4.4 0.4.3 0.4.2 0.4.1 0.4.0 0.3.4 0.3.3 0.3.2 0.3.1

Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise

Current section

Files

Jump to
macula src macula_membership_gossip.erl
Raw

src/macula_membership_gossip.erl

%%%-------------------------------------------------------------------
%%% @doc
%%% Gossip dissemination for SWIM protocol.
%%% Tracks membership updates and provides them for piggybacking.
%%% Uses exponential decay: log(N) messages per update.
%%% @end
%%%-------------------------------------------------------------------
-module(macula_membership_gossip).
%% API
-export([
new/0,
add_update/4,
get_updates/2,
mark_transmitted/2,
merge_updates/2,
prune/2
]).
%% Types
-type update() :: {binary(), macula_membership_member:status(), non_neg_integer(), non_neg_integer()}.
-type gossip_state() :: #{
updates => #{binary() => {macula_membership_member:status(), non_neg_integer(), non_neg_integer(), integer()}}
}.
-export_type([update/0, gossip_state/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Create a new gossip state.
-spec new() -> gossip_state().
new() ->
#{updates => #{}}.
%% @doc Add a membership update to gossip.
%% If a more recent update exists, it's replaced.
-spec add_update(gossip_state(), binary(), macula_membership_member:status(), non_neg_integer()) ->
gossip_state().
add_update(#{updates := Updates} = State, NodeId, Status, Incarnation) ->
Timestamp = erlang:system_time(millisecond),
NewUpdate = case maps:get(NodeId, Updates, undefined) of
undefined ->
%% New update
{Status, Incarnation, 0, Timestamp};
{OldStatus, OldInc, TransmitCount, _OldTimestamp} ->
if
Incarnation > OldInc ->
%% Higher incarnation, replace
{Status, Incarnation, 0, Timestamp};
Incarnation =:= OldInc andalso Status =/= OldStatus ->
%% Same incarnation, different status (suspect > alive)
{Status, Incarnation, 0, Timestamp};
true ->
%% Same or older, keep existing
{OldStatus, OldInc, TransmitCount, _OldTimestamp}
end
end,
State#{updates => Updates#{NodeId => NewUpdate}}.
%% @doc Get updates to piggyback on messages.
%% Returns most recent updates first, limited by max_updates.
-spec get_updates(gossip_state(), pos_integer()) -> [update()].
get_updates(#{updates := Updates}, MaxUpdates) ->
%% Convert to list with timestamps for sorting
UpdateList = [{NodeId, Status, Inc, TransmitCount, Timestamp} ||
{NodeId, {Status, Inc, TransmitCount, Timestamp}} <- maps:to_list(Updates)],
%% Sort by timestamp (most recent first)
Sorted = lists:sort(
fun({_, _, _, _, T1}, {_, _, _, _, T2}) -> T1 >= T2 end,
UpdateList
),
%% Take max and format
[{NodeId, Status, Inc, TransmitCount} ||
{NodeId, Status, Inc, TransmitCount, _} <- lists:sublist(Sorted, MaxUpdates)].
%% @doc Mark an update as transmitted (increment transmit count).
-spec mark_transmitted(gossip_state(), binary()) -> gossip_state().
mark_transmitted(#{updates := Updates} = State, NodeId) ->
case maps:get(NodeId, Updates, undefined) of
undefined ->
State; % Not found
{Status, Inc, TransmitCount, Timestamp} ->
NewUpdate = {Status, Inc, TransmitCount + 1, Timestamp},
State#{updates => Updates#{NodeId => NewUpdate}}
end.
%% @doc Merge received gossip updates into local state.
%% Uses SWIM merge semantics (higher incarnation wins, etc.).
-spec merge_updates(gossip_state(), [{binary(), macula_membership_member:status(), non_neg_integer()}]) ->
gossip_state().
merge_updates(State, ReceivedUpdates) ->
lists:foldl(
fun({NodeId, Status, Incarnation}, Acc) ->
add_update(Acc, NodeId, Status, Incarnation)
end,
State,
ReceivedUpdates
).
%% @doc Prune updates that have been transmitted enough times.
%% Target is typically log(N) where N is cluster size.
-spec prune(gossip_state(), non_neg_integer()) -> gossip_state().
prune(#{updates := Updates} = State, TargetTransmitCount) ->
%% Remove updates with transmit count >= target
Pruned = maps:filter(
fun(_NodeId, {_Status, _Inc, TransmitCount, _Timestamp}) ->
TransmitCount < TargetTransmitCount
end,
Updates
),
State#{updates => Pruned}.