|
@@ -29,6 +29,9 @@
|
|
|
%% API
|
|
|
-export([start_link/1]).
|
|
|
-export([get_subcluster_nodes/1]).
|
|
|
+-export([join/2, join/3, join/4]).
|
|
|
+-export([get_members/1, get_members/2]).
|
|
|
+-export([count/1, count/2]).
|
|
|
|
|
|
%% syn_gen_scope callbacks
|
|
|
-export([
|
|
@@ -55,6 +58,83 @@ start_link(Scope) when is_atom(Scope) ->
|
|
|
get_subcluster_nodes(Scope) ->
|
|
|
syn_gen_scope:get_subcluster_nodes(?MODULE, Scope).
|
|
|
|
|
|
+-spec get_members(GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
|
|
|
+get_members(GroupName) ->
|
|
|
+ get_members(default, GroupName).
|
|
|
+
|
|
|
+-spec get_members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
|
|
|
+get_members(Scope, GroupName) ->
|
|
|
+ case syn_backbone:get_table_name(syn_groups_by_name, Scope) of
|
|
|
+ undefined ->
|
|
|
+ error({invalid_scope, Scope});
|
|
|
+
|
|
|
+ TableByName ->
|
|
|
+ ets:select(TableByName, [{
|
|
|
+ {{GroupName, '$2'}, '$3', '_', '_', '_'},
|
|
|
+ [],
|
|
|
+ [{{'$2', '$3'}}]
|
|
|
+ }])
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec join(GroupName :: term(), Pid :: pid()) -> ok.
|
|
|
+join(GroupName, Pid) ->
|
|
|
+ join(GroupName, Pid, undefined).
|
|
|
+
|
|
|
+-spec join(GroupNameOrScope :: term(), PidOrGroupName :: term(), MetaOrPid :: term()) -> ok.
|
|
|
+join(GroupName, Pid, Meta) when is_pid(Pid) ->
|
|
|
+ join(default, GroupName, Pid, Meta);
|
|
|
+
|
|
|
+join(Scope, GroupName, Pid) when is_pid(Pid) ->
|
|
|
+ join(Scope, GroupName, Pid, undefined).
|
|
|
+
|
|
|
+-spec join(Scope :: atom(), GroupName :: term(), Pid :: pid(), Meta :: term()) -> ok.
|
|
|
+join(Scope, GroupName, Pid, Meta) ->
|
|
|
+ Node = node(Pid),
|
|
|
+ case syn_gen_scope:call(?MODULE, Node, Scope, {join_on_owner, node(), GroupName, Pid, Meta}) of
|
|
|
+ {ok, {Time, TableByName, TableByPid}} when Node =/= node() ->
|
|
|
+ %% update table on caller node immediately so that subsequent calls have an updated registry
|
|
|
+ add_to_local_table(GroupName, Pid, Meta, Time, undefined, TableByName, TableByPid),
|
|
|
+ %% callback
|
|
|
+ %%syn_event_handler:do_on_process_joined(Scope, GroupName, {TablePid, TableMeta}, {Pid, Meta}),
|
|
|
+ %% return
|
|
|
+ ok;
|
|
|
+
|
|
|
+ {Response, _} ->
|
|
|
+ Response
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec count(Scope :: atom()) -> non_neg_integer().
|
|
|
+count(Scope) ->
|
|
|
+ case syn_backbone:get_table_name(syn_groups_by_name, Scope) of
|
|
|
+ undefined ->
|
|
|
+ error({invalid_scope, Scope});
|
|
|
+
|
|
|
+ TableByName ->
|
|
|
+ Entries = ets:select(TableByName, [{
|
|
|
+ {{'$1', '_'}, '_', '_', '_', '_'},
|
|
|
+ [],
|
|
|
+ ['$1']
|
|
|
+ }]),
|
|
|
+ Set = sets:from_list(Entries),
|
|
|
+ sets:size(Set)
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec count(Scope :: atom(), Node :: node()) -> non_neg_integer().
|
|
|
+count(Scope, Node) ->
|
|
|
+ case syn_backbone:get_table_name(syn_groups_by_name, Scope) of
|
|
|
+ undefined ->
|
|
|
+ error({invalid_scope, Scope});
|
|
|
+
|
|
|
+ TableByName ->
|
|
|
+ Entries = ets:select(TableByName, [{
|
|
|
+ {{'$1', '_'}, '_', '_', '_', Node},
|
|
|
+ [],
|
|
|
+ ['$1']
|
|
|
+ }]),
|
|
|
+ Set = sets:from_list(Entries),
|
|
|
+ sets:size(Set)
|
|
|
+ end.
|
|
|
+
|
|
|
%% ===================================================================
|
|
|
%% Callbacks
|
|
|
%% ===================================================================
|
|
@@ -78,6 +158,32 @@ init(_State) ->
|
|
|
{noreply, #state{}, timeout() | hibernate | {continue, term()}} |
|
|
|
{stop, Reason :: term(), Reply :: term(), #state{}} |
|
|
|
{stop, Reason :: term(), #state{}}.
|
|
|
+handle_call({join_on_owner, RequesterNode, GroupName, Pid, Meta}, _From, #state{
|
|
|
+ scope = Scope,
|
|
|
+ table_by_name = TableByName,
|
|
|
+ table_by_pid = TableByPid
|
|
|
+} = State) ->
|
|
|
+ case is_process_alive(Pid) of
|
|
|
+ true ->
|
|
|
+ %% available
|
|
|
+ MRef = case find_monitor_for_pid(Pid, TableByPid) of
|
|
|
+ undefined -> erlang:monitor(process, Pid); %% process is not monitored yet, add
|
|
|
+ MRef0 -> MRef0
|
|
|
+ end,
|
|
|
+ %% add to local table
|
|
|
+ Time = erlang:system_time(),
|
|
|
+ add_to_local_table(GroupName, Pid, Meta, Time, MRef, TableByName, TableByPid),
|
|
|
+ %% callback
|
|
|
+ %%syn_event_handler:do_on_process_joined(Scope, GroupName, {undefined, undefined}, {Pid, Meta}),
|
|
|
+ %% broadcast
|
|
|
+ syn_gen_scope:broadcast({'3.0', sync_join, GroupName, Pid, Meta, Time}, [RequesterNode], State),
|
|
|
+ %% return
|
|
|
+ {reply, {ok, {Time, TableByName, TableByPid}}, State};
|
|
|
+
|
|
|
+ false ->
|
|
|
+ {reply, {{error, not_alive}, undefined}, State}
|
|
|
+ end;
|
|
|
+
|
|
|
handle_call(Request, From, State) ->
|
|
|
error_logger:warning_msg("SYN[~s] Received from ~p an unknown call message: ~p", [node(), From, Request]),
|
|
|
{reply, undefined, State}.
|
|
@@ -89,6 +195,30 @@ handle_call(Request, From, State) ->
|
|
|
{noreply, #state{}} |
|
|
|
{noreply, #state{}, timeout() | hibernate | {continue, term()}} |
|
|
|
{stop, Reason :: term(), #state{}}.
|
|
|
+handle_info({'3.0', sync_join, GroupName, Pid, Meta, Time}, #state{
|
|
|
+ table_by_name = TableByName,
|
|
|
+ table_by_pid = TableByPid
|
|
|
+} = State) ->
|
|
|
+ case find_groups_entry_by_name_and_pid(GroupName, Pid, TableByName) of
|
|
|
+ undefined ->
|
|
|
+ %% new
|
|
|
+ add_to_local_table(GroupName, Pid, Meta, Time, undefined, TableByName, TableByPid),
|
|
|
+ %% callback
|
|
|
+ %%syn_event_handler:do_on_process_joined(Scope, GroupName, {undefined, undefined}, {Pid, Meta});
|
|
|
+ {noreply, State};
|
|
|
+
|
|
|
+ {GroupName, Pid, _TableMeta, TableTime, _MRef, _TableNode} when Time > TableTime ->
|
|
|
+ %% update meta
|
|
|
+ add_to_local_table(GroupName, Pid, Meta, Time, undefined, TableByName, TableByPid),
|
|
|
+ %% callback
|
|
|
+ %%syn_event_handler:do_on_process_joined(Scope, GroupName, {undefined, undefined}, {Pid, Meta});
|
|
|
+ {noreply, State};
|
|
|
+
|
|
|
+ {GroupName, Pid, _TableMeta, _TableTime, _TableMRef, _TableNode} ->
|
|
|
+ %% race condition: incoming data is older, ignore
|
|
|
+ {noreply, State}
|
|
|
+ end;
|
|
|
+
|
|
|
handle_info(Info, State) ->
|
|
|
error_logger:warning_msg("SYN[~s] Received an unknown info message: ~p", [node(), Info]),
|
|
|
{noreply, State}.
|
|
@@ -116,3 +246,37 @@ purge_local_data_for_node(Node, #state{
|
|
|
%% ===================================================================
|
|
|
%% Internal
|
|
|
%% ===================================================================
|
|
|
+-spec find_monitor_for_pid(Pid :: pid(), TableByPid :: atom()) -> reference() | undefined.
|
|
|
+find_monitor_for_pid(Pid, TableByPid) when is_pid(Pid) ->
|
|
|
+ %% we use select instead of lookup to limit the results and thus cover the case
|
|
|
+ %% when a process is registered with a considerable amount of names
|
|
|
+ case ets:select(TableByPid, [{
|
|
|
+ {{Pid, '_'}, '_', '_', '$5', '_'},
|
|
|
+ [],
|
|
|
+ ['$5']
|
|
|
+ }], 1) of
|
|
|
+ {[MRef], _} -> MRef;
|
|
|
+ '$end_of_table' -> undefined
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec find_groups_entry_by_name_and_pid(GroupName :: any(), Pid :: pid(), TableByName :: atom()) ->
|
|
|
+ Entry :: syn_groups_entry() | undefined.
|
|
|
+find_groups_entry_by_name_and_pid(GroupName, Pid, TableByName) ->
|
|
|
+ case ets:lookup(TableByName, {GroupName, Pid}) of
|
|
|
+ [] -> undefined;
|
|
|
+ [Entry] -> Entry
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec add_to_local_table(
|
|
|
+ GroupName :: term(),
|
|
|
+ Pid :: pid(),
|
|
|
+ Meta :: term(),
|
|
|
+ Time :: integer(),
|
|
|
+ MRef :: undefined | reference(),
|
|
|
+ TableByName :: atom(),
|
|
|
+ TableByPid :: atom()
|
|
|
+) -> true.
|
|
|
+add_to_local_table(GroupName, Pid, Meta, Time, MRef, TableByName, TableByPid) ->
|
|
|
+ %% insert
|
|
|
+ ets:insert(TableByName, {{GroupName, Pid}, Meta, Time, MRef, node(Pid)}),
|
|
|
+ ets:insert(TableByPid, {{Pid, GroupName}, Meta, Time, MRef, node(Pid)}).
|