Packages
macula
0.6.7
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
Current section
Files
src/macula_membership_list.erl
%%%-------------------------------------------------------------------
%%% @doc
%%% Membership list for SWIM protocol.
%%% Maintains cluster membership view with fast concurrent access.
%%% Uses map-based storage (could be ETS in production).
%%% @end
%%%-------------------------------------------------------------------
-module(macula_membership_list).
%% API
-export([
new/1,
add_member/2,
update_member/2,
remove_member/2,
get_member/2,
get_alive_members/1,
get_suspect_members/1,
get_random_members/2,
get_random_members/3,
get_all_members/1,
size/1
]).
%% Types
-type member_list() :: #{
members := #{binary() => macula_membership_member:member()}
}.
-export_type([member_list/0]).
%%%===================================================================
%%% API Functions
%%%===================================================================
%% @doc Create a new membership list with the local node.
-spec new(macula_membership_member:member()) -> member_list().
new(LocalMember) ->
NodeId = macula_membership_member:node_id(LocalMember),
#{
members => #{NodeId => LocalMember}
}.
%% @doc Add a new member to the list.
%% If member already exists, this is a no-op (use update_member instead).
-spec add_member(member_list(), macula_membership_member:member()) -> member_list().
add_member(#{members := Members} = List, Member) ->
NodeId = macula_membership_member:node_id(Member),
case maps:is_key(NodeId, Members) of
true ->
%% Already exists, don't overwrite
List;
false ->
List#{members => Members#{NodeId => Member}}
end.
%% @doc Update an existing member (or add if not present).
%% Uses merge semantics to resolve conflicts.
-spec update_member(member_list(), macula_membership_member:member()) -> member_list().
update_member(#{members := Members} = List, NewMember) ->
NodeId = macula_membership_member:node_id(NewMember),
update_or_add_member(maps:get(NodeId, Members, undefined), List, Members, NodeId, NewMember).
%% @doc Add new member or merge with existing.
update_or_add_member(undefined, List, Members, NodeId, NewMember) ->
%% Not present, add it
List#{members => Members#{NodeId => NewMember}};
update_or_add_member(OldMember, List, Members, NodeId, NewMember) ->
%% Merge with existing
Merged = macula_membership_member:merge(NewMember, OldMember),
List#{members => Members#{NodeId => Merged}}.
%% @doc Remove a member from the list.
-spec remove_member(member_list(), binary()) -> member_list().
remove_member(#{members := Members} = List, NodeId) ->
List#{members => maps:remove(NodeId, Members)}.
%% @doc Get a member by node ID.
-spec get_member(member_list(), binary()) -> {ok, macula_membership_member:member()} | not_found.
get_member(#{members := Members}, NodeId) ->
get_member_result(maps:get(NodeId, Members, undefined)).
%% @doc Convert maps:get result to API result.
get_member_result(undefined) -> not_found;
get_member_result(Member) -> {ok, Member}.
%% @doc Get all alive members.
-spec get_alive_members(member_list()) -> [macula_membership_member:member()].
get_alive_members(#{members := Members}) ->
[M || M <- maps:values(Members), macula_membership_member:status(M) =:= alive].
%% @doc Get all suspect members.
-spec get_suspect_members(member_list()) -> [macula_membership_member:member()].
get_suspect_members(#{members := Members}) ->
[M || M <- maps:values(Members), macula_membership_member:status(M) =:= suspect].
%% @doc Get N random members from the list.
-spec get_random_members(member_list(), pos_integer()) -> [macula_membership_member:member()].
get_random_members(List, N) ->
get_random_members(List, N, undefined).
%% @doc Get N random members excluding specified node ID.
-spec get_random_members(member_list(), pos_integer(), binary() | undefined) ->
[macula_membership_member:member()].
get_random_members(#{members := Members}, N, ExcludeNodeId) ->
%% Get candidate members
Candidates = case ExcludeNodeId of
undefined ->
maps:values(Members);
NodeId ->
[M || {K, M} <- maps:to_list(Members), K =/= NodeId]
end,
%% Shuffle and take N
Shuffled = shuffle(Candidates),
lists:sublist(Shuffled, N).
%% @doc Get all members.
-spec get_all_members(member_list()) -> [macula_membership_member:member()].
get_all_members(#{members := Members}) ->
maps:values(Members).
%% @doc Get the number of members in the list.
-spec size(member_list()) -> non_neg_integer().
size(#{members := Members}) ->
maps:size(Members).
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @doc Shuffle a list (Fisher-Yates algorithm).
-spec shuffle(list()) -> list().
shuffle(List) ->
List1 = lists:zip(List, [rand:uniform() || _ <- List]),
List2 = lists:sort(fun({_, R1}, {_, R2}) -> R1 =< R2 end, List1),
[E || {E, _} <- List2].