Browse Source

Merge data from joining groups after netsplits.

Roberto Ostinelli 3 years ago
parent
commit
30921bac12
4 changed files with 299 additions and 40 deletions
  1. 53 29
      src/syn_groups.erl
  2. 6 6
      src/syn_registry.erl
  3. 237 5
      test/syn_groups_SUITE.erl
  4. 3 0
      test/syn_registry_SUITE.erl

+ 53 - 29
src/syn_groups.erl

@@ -188,7 +188,6 @@ init(State) ->
     {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) ->
@@ -243,29 +242,9 @@ 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({'3.0', sync_join, GroupName, Pid, Meta, Time}, State) ->
+    handle_groups_sync(GroupName, Pid, Meta, Time, State),
+    {noreply, State};
 
 handle_info({'3.0', sync_leave, GroupName, Pid}, #state{
     table_by_name = TableByName,
@@ -288,7 +267,7 @@ handle_info({'DOWN', _MRef, process, Pid, Reason}, #state{
             );
 
         Entries ->
-            lists:foreach(fun({{_Pid, GroupName}, Meta, _, _, _}) ->
+            lists:foreach(fun({{_Pid, GroupName}, _Meta, _, _, _}) ->
                 %% remove from table
                 remove_from_local_table(GroupName, Pid, TableByName, TableByPid),
                 %% callback
@@ -309,12 +288,14 @@ handle_info(Info, State) ->
 %% ----------------------------------------------------------------------------------------------------------
 -spec get_local_data(State :: term()) -> {ok, Data :: any()} | undefined.
 get_local_data(#state{table_by_name = TableByName}) ->
-    {ok, []}.
+    {ok, get_groups_tuples_for_node(node(), TableByName)}.
 
 -spec save_remote_data(RemoteData :: any(), State :: term()) -> any().
-save_remote_data(RegistryTuplesOfRemoteNode, #state{scope = Scope} = State) ->
+save_remote_data(GroupsTuplesOfRemoteNode, State) ->
     %% insert tuples
-    ok.
+    lists:foreach(fun({GroupName, Pid, Meta, Time}) ->
+        handle_groups_sync(GroupName, Pid, Meta, Time, State)
+    end, GroupsTuplesOfRemoteNode).
 
 -spec purge_local_data_for_node(Node :: node(), State :: term()) -> any().
 purge_local_data_for_node(Node, #state{
@@ -322,7 +303,7 @@ purge_local_data_for_node(Node, #state{
     table_by_name = TableByName,
     table_by_pid = TableByPid
 }) ->
-    ok.
+    purge_groups_for_remote_node(Scope, Node, TableByName, TableByPid).
 
 %% ===================================================================
 %% Internal
@@ -437,3 +418,46 @@ add_to_local_table(GroupName, Pid, Meta, Time, MRef, TableByName, TableByPid) ->
 remove_from_local_table(GroupName, Pid, TableByName, TableByPid) ->
     true = ets:delete(TableByName, {GroupName, Pid}),
     true = ets:delete(TableByPid, {Pid, GroupName}).
+
+-spec purge_groups_for_remote_node(Scope :: atom(), Node :: atom(), TableByName :: atom(), TableByPid :: atom()) -> true.
+purge_groups_for_remote_node(_Scope, Node, TableByName, TableByPid) ->
+%%    %% loop elements for callback in a separate process to free scope process
+%%    GroupsTuples = get_groups_tuples_for_node(Node, TableByName),
+%%    spawn(fun() ->
+%%        lists:foreach(fun({Name, Pid, Meta, _Time}) ->
+%%            syn_event_handler:do_on_process_left(Scope, Name, Pid, Meta)
+%%        end, GroupsTuples)
+%%    end),
+    ets:match_delete(TableByName, {{'_', '_'}, '_', '_', '_', Node}),
+    ets:match_delete(TableByPid, {{'_', '_'}, '_', '_', '_', Node}).
+
+-spec handle_groups_sync(
+    GroupName :: term(),
+    Pid :: pid(),
+    Meta :: term(),
+    Time :: non_neg_integer(),
+    #state{}
+) -> any().
+handle_groups_sync(GroupName, Pid, Meta, Time, #state{
+    table_by_name = TableByName,
+    table_by_pid = TableByPid
+}) ->
+    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});
+            ok;
+
+        {{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});
+            ok;
+
+        {{GroupName, Pid}, _TableMeta, _TableTime, _TableMRef, _TableNode} ->
+            %% race condition: incoming data is older, ignore
+            ok
+    end.

+ 6 - 6
src/syn_registry.erl

@@ -268,8 +268,8 @@ handle_call(Request, From, State) ->
     {noreply, #state{}} |
     {noreply, #state{}, timeout() | hibernate | {continue, term()}} |
     {stop, Reason :: term(), #state{}}.
-handle_info({'3.0', sync_register, Scope, Name, Pid, Meta, Time}, State) ->
-    handle_registry_sync(Scope, Name, Pid, Meta, Time, State),
+handle_info({'3.0', sync_register, _Scope, Name, Pid, Meta, Time}, State) ->
+    handle_registry_sync(Name, Pid, Meta, Time, State),
     {noreply, State};
 
 handle_info({'3.0', sync_unregister, Name, Pid, Meta}, #state{
@@ -320,10 +320,10 @@ get_local_data(#state{table_by_name = TableByName}) ->
     {ok, get_registry_tuples_for_node(node(), TableByName)}.
 
 -spec save_remote_data(RemoteData :: term(), #state{}) -> any().
-save_remote_data(RegistryTuplesOfRemoteNode, #state{scope = Scope} = State) ->
+save_remote_data(RegistryTuplesOfRemoteNode, State) ->
     %% insert tuples
     lists:foreach(fun({Name, Pid, Meta, Time}) ->
-        handle_registry_sync(Scope, Name, Pid, Meta, Time, State)
+        handle_registry_sync(Name, Pid, Meta, Time, State)
     end, RegistryTuplesOfRemoteNode).
 
 -spec purge_local_data_for_node(Node :: node(), #state{}) -> any().
@@ -477,14 +477,14 @@ purge_registry_for_remote_node(Scope, Node, TableByName, TableByPid) when Node =
     true = ets:match_delete(TableByPid, {'_', '_', '_', '_', '_', Node}).
 
 -spec handle_registry_sync(
-    Scope :: atom(),
     Name :: term(),
     Pid :: pid(),
     Meta :: term(),
     Time :: non_neg_integer(),
     #state{}
 ) -> any().
-handle_registry_sync(Scope, Name, Pid, Meta, Time, #state{
+handle_registry_sync(Name, Pid, Meta, Time, #state{
+    scope = Scope,
     table_by_name = TableByName,
     table_by_pid = TableByPid
 } = State) ->

+ 237 - 5
test/syn_groups_SUITE.erl

@@ -36,7 +36,8 @@
     three_nodes_discover_default_scope/1,
     three_nodes_discover_custom_scope/1,
     three_nodes_join_leave_and_monitor_default_scope/1,
-    three_nodes_join_leave_and_monitor_custom_scope/1
+    three_nodes_join_leave_and_monitor_custom_scope/1,
+    three_nodes_cluster_changes/1
 ]).
 
 %% include
@@ -74,10 +75,11 @@ all() ->
 groups() ->
     [
         {three_nodes_groups, [shuffle], [
-            three_nodes_discover_default_scope,
-            three_nodes_discover_custom_scope,
-            three_nodes_join_leave_and_monitor_default_scope,
-            three_nodes_join_leave_and_monitor_custom_scope
+%%            three_nodes_discover_default_scope,
+%%            three_nodes_discover_custom_scope,
+%%            three_nodes_join_leave_and_monitor_default_scope,
+%%            three_nodes_join_leave_and_monitor_custom_scope,
+            three_nodes_cluster_changes
         ]}
     ].
 %% -------------------------------------------------------------------
@@ -708,3 +710,233 @@ three_nodes_join_leave_and_monitor_custom_scope(Config) ->
 
     %% errors
     {error, not_in_group} = syn:leave(custom_scope_ab, {group, "one"}, PidWithMeta).
+
+three_nodes_cluster_changes(Config) ->
+    %% get slaves
+    SlaveNode1 = proplists:get_value(slave_node_1, Config),
+    SlaveNode2 = proplists:get_value(slave_node_2, Config),
+
+    %% disconnect 1 from 2
+    rpc:call(SlaveNode1, syn_test_suite_helper, disconnect_node, [SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(node(), [SlaveNode1, SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(SlaveNode1, [node()]),
+    syn_test_suite_helper:assert_cluster(SlaveNode2, [node()]),
+
+    %% start syn on 1 and 2, nodes don't know of each other
+    ok = rpc:call(SlaveNode1, syn, start, []),
+    ok = rpc:call(SlaveNode2, syn, start, []),
+
+    %% add custom scopes
+    ok = rpc:call(SlaveNode1, syn, add_node_to_scopes, [[custom_scope_bc]]),
+    ok = rpc:call(SlaveNode2, syn, add_node_to_scopes, [[custom_scope_bc]]),
+
+    %% start processes
+    PidRemoteOn1 = syn_test_suite_helper:start_process(SlaveNode1),
+    PidRemoteOn2 = syn_test_suite_helper:start_process(SlaveNode2),
+
+    %% join
+    ok = rpc:call(SlaveNode1, syn, join, [<<"common-group">>, PidRemoteOn1, "meta-1"]),
+    ok = rpc:call(SlaveNode2, syn, join, [<<"common-group">>, PidRemoteOn2, "meta-2"]),
+    ok = rpc:call(SlaveNode2, syn, join, [<<"group-2">>, PidRemoteOn2, "other-meta"]),
+    ok = rpc:call(SlaveNode1, syn, join, [custom_scope_bc, <<"scoped-on-bc">>, PidRemoteOn1, "scoped-meta-1"]),
+    ok = rpc:call(SlaveNode2, syn, join, [custom_scope_bc, <<"scoped-on-bc">>, PidRemoteOn2, "scoped-meta-2"]),
+
+    %% form full cluster
+    ok = syn:start(),
+    rpc:call(SlaveNode1, syn_test_suite_helper, connect_node, [SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(node(), [SlaveNode1, SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(SlaveNode1, [node(), SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(SlaveNode2, [node(), SlaveNode1]),
+    syn_test_suite_helper:wait_process_name_ready(syn_groups_default),
+
+    %% retrieve
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "meta-1"}, {PidRemoteOn2, "meta-2"}]),
+        fun() -> lists:sort(syn:get_members(<<"common-group">>)) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "meta-1"}, {PidRemoteOn2, "meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [<<"common-group">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "meta-1"}, {PidRemoteOn2, "meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [<<"common-group">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(syn:get_members(<<"group-2">>)) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [<<"group-2">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [<<"group-2">>])) end
+    ),
+    2 = syn:groups_count(default),
+    0 = syn:groups_count(default, node()),
+    1 = syn:groups_count(default, SlaveNode1),
+    2 = syn:groups_count(default, SlaveNode2),
+    2 = rpc:call(SlaveNode1, syn, groups_count, [default]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [default, node()]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [default, SlaveNode1]),
+    2 = rpc:call(SlaveNode1, syn, groups_count, [default, SlaveNode2]),
+    2 = rpc:call(SlaveNode2, syn, groups_count, [default]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [default, node()]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [default, SlaveNode1]),
+    2 = rpc:call(SlaveNode2, syn, groups_count, [default, SlaveNode2]),
+
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "scoped-meta-1"}, {PidRemoteOn2, "scoped-meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [custom_scope_bc, <<"scoped-on-bc">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "scoped-meta-1"}, {PidRemoteOn2, "scoped-meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [custom_scope_bc, <<"scoped-on-bc">>])) end
+    ),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, node()),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, SlaveNode1),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, SlaveNode2),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, node()]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, SlaveNode1]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, SlaveNode2]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, node()]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, SlaveNode1]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, SlaveNode2]),
+
+    %% partial netsplit (1 cannot see 2)
+    rpc:call(SlaveNode1, syn_test_suite_helper, disconnect_node, [SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(node(), [SlaveNode1, SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(SlaveNode1, [node()]),
+    syn_test_suite_helper:assert_cluster(SlaveNode2, [node()]),
+
+    %% retrieve
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "meta-1"}, {PidRemoteOn2, "meta-2"}]),
+        fun() -> lists:sort(syn:get_members(<<"common-group">>)) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn1, "meta-1"}],
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [<<"common-group">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "meta-2"}],
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [<<"common-group">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(syn:get_members(<<"group-2">>)) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [],
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [<<"group-2">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [<<"group-2">>])) end
+    ),
+    2 = syn:groups_count(default),
+    0 = syn:groups_count(default, node()),
+    1 = syn:groups_count(default, SlaveNode1),
+    2 = syn:groups_count(default, SlaveNode2),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [default]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [default, node()]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [default, SlaveNode1]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [default, SlaveNode2]),
+    2 = rpc:call(SlaveNode2, syn, groups_count, [default]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [default, node()]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [default, SlaveNode1]),
+    2 = rpc:call(SlaveNode2, syn, groups_count, [default, SlaveNode2]),
+
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn1, "scoped-meta-1"}],
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [custom_scope_bc, <<"scoped-on-bc">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "scoped-meta-2"}],
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [custom_scope_bc, <<"scoped-on-bc">>])) end
+    ),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, node()),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, SlaveNode1),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, SlaveNode2),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, node()]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, SlaveNode1]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, SlaveNode2]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, node()]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, SlaveNode1]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, SlaveNode2]),
+
+    %% re-join
+    rpc:call(SlaveNode1, syn_test_suite_helper, connect_node, [SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(node(), [SlaveNode1, SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(SlaveNode1, [node(), SlaveNode2]),
+    syn_test_suite_helper:assert_cluster(SlaveNode2, [node(), SlaveNode1]),
+
+    %% retrieve
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "meta-1"}, {PidRemoteOn2, "meta-2"}]),
+        fun() -> lists:sort(syn:get_members(<<"common-group">>)) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "meta-1"}, {PidRemoteOn2, "meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [<<"common-group">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "meta-1"}, {PidRemoteOn2, "meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [<<"common-group">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(syn:get_members(<<"group-2">>)) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [<<"group-2">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        [{PidRemoteOn2, "other-meta"}],
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [<<"group-2">>])) end
+    ),
+    2 = syn:groups_count(default),
+    0 = syn:groups_count(default, node()),
+    1 = syn:groups_count(default, SlaveNode1),
+    2 = syn:groups_count(default, SlaveNode2),
+    2 = rpc:call(SlaveNode1, syn, groups_count, [default]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [default, node()]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [default, SlaveNode1]),
+    2 = rpc:call(SlaveNode1, syn, groups_count, [default, SlaveNode2]),
+    2 = rpc:call(SlaveNode2, syn, groups_count, [default]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [default, node()]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [default, SlaveNode1]),
+    2 = rpc:call(SlaveNode2, syn, groups_count, [default, SlaveNode2]),
+
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "scoped-meta-1"}, {PidRemoteOn2, "scoped-meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode1, syn, get_members, [custom_scope_bc, <<"scoped-on-bc">>])) end
+    ),
+    syn_test_suite_helper:assert_wait(
+        lists:sort([{PidRemoteOn1, "scoped-meta-1"}, {PidRemoteOn2, "scoped-meta-2"}]),
+        fun() -> lists:sort(rpc:call(SlaveNode2, syn, get_members, [custom_scope_bc, <<"scoped-on-bc">>])) end
+    ),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, node()),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, SlaveNode1),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:groups_count(custom_scope_bc, SlaveNode2),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc]),
+    0 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, node()]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, SlaveNode1]),
+    1 = rpc:call(SlaveNode1, syn, groups_count, [custom_scope_bc, SlaveNode2]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc]),
+    0 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, node()]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, SlaveNode1]),
+    1 = rpc:call(SlaveNode2, syn, groups_count, [custom_scope_bc, SlaveNode2]).

+ 3 - 0
test/syn_registry_SUITE.erl

@@ -923,6 +923,9 @@ three_nodes_cluster_changes(Config) ->
         fun() -> rpc:call(SlaveNode2, syn, lookup, [custom_scope_bc, "BC-proc-1 alias"]) end
     ),
     {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:registry_count(custom_scope_bc),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:registry_count(custom_scope_bc, node()),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:registry_count(custom_scope_bc, SlaveNode1),
+    {'EXIT', {{invalid_scope, custom_scope_bc}, _}} = catch syn:registry_count(custom_scope_bc, SlaveNode2),
     2 = rpc:call(SlaveNode1, syn, registry_count, [custom_scope_bc]),
     0 = rpc:call(SlaveNode1, syn, registry_count, [custom_scope_bc, node()]),
     2 = rpc:call(SlaveNode1, syn, registry_count, [custom_scope_bc, SlaveNode1]),