File 2620-pg-fix-empty-group-removal-from-internal-pg-state.patch of Package erlang

From 6c4464028b4f7d83ef617dd65af762d5ff112ae3 Mon Sep 17 00:00:00 2001
From: Maxim Fedorov <dane@whatsapp.com>
Date: Thu, 11 Mar 2021 13:25:11 -0800
Subject: [PATCH] pg: fix empty group removal from internal pg state

pg keeps two copies of state: gen_server state and ETS table for
concurrent access. #2866 fixed one potential way to introduce
mismatch between internal state and ETS table, and this patch
completes the fix.
---
 lib/kernel/src/pg.erl        | 27 ++++++++++++++-------------
 lib/kernel/test/pg_SUITE.erl | 10 ++++++++++
 2 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/lib/kernel/src/pg.erl b/lib/kernel/src/pg.erl
index 2ca0b083ad..573d2e6953 100644
--- a/lib/kernel/src/pg.erl
+++ b/lib/kernel/src/pg.erl
@@ -281,19 +281,7 @@ handle_info({leave, Peer, PidOrPids, Groups}, #state{scope = Scope, nodes = Node
     case maps:get(Peer, Nodes, []) of
         {MRef, RemoteMap} ->
             _ = leave_remote(Scope, PidOrPids, Groups),
-            NewRemoteMap = lists:foldl(
-                fun (Group, Acc) ->
-                    case maps:get(Group, Acc) of
-                        PidOrPids ->
-                            maps:remove(Group, Acc);
-                        [PidOrPids] ->
-                            maps:remove(Group, Acc);
-                        Existing when is_pid(PidOrPids) ->
-                            Acc#{Group => lists:delete(PidOrPids, Existing)};
-                        Existing ->
-                            Acc#{Group => Existing-- PidOrPids}
-                    end
-                end, RemoteMap, Groups),
+            NewRemoteMap = leave_update_remote_map(PidOrPids, RemoteMap, Groups),
             {noreply, State#state{nodes = Nodes#{Peer => {MRef, NewRemoteMap}}}};
         [] ->
             %% Handle race condition: remote node disconnected, but scope process
@@ -524,6 +512,19 @@ leave_remote(Scope, Pids, Groups) ->
         end ||
         Group <- Groups].
 
+leave_update_remote_map(Pid, RemoteMap, Groups) when is_pid(Pid) ->
+    leave_update_remote_map([Pid], RemoteMap, Groups);
+leave_update_remote_map(Pids, RemoteMap, Groups) ->
+    lists:foldl(
+        fun (Group, Acc) ->
+            case maps:get(Group, Acc) -- Pids of
+                [] ->
+                    maps:remove(Group, Acc);
+                Remaining ->
+                    Acc#{Group => Remaining}
+            end
+        end, RemoteMap, Groups).
+
 all_local_pids(Monitors) ->
     maps:to_list(maps:fold(
         fun(Pid, {_Ref, Groups}, Acc) ->
diff --git a/lib/kernel/test/pg_SUITE.erl b/lib/kernel/test/pg_SUITE.erl
index f03b8a6a39..3a69e33bcf 100644
--- a/lib/kernel/test/pg_SUITE.erl
+++ b/lib/kernel/test/pg_SUITE.erl
@@ -295,6 +295,16 @@ empty_group_by_remote_leave(Config) when is_list(Config) ->
     % empty group should be deleted.
     ?assertEqual(#{}, NewRemoteMap),
 
+    %% another variant of emptying a group remotely: join([Pi1, Pid2]) and leave ([Pid2, Pid1])
+    RemotePid2 = erlang:spawn(TwoPeer, forever()),
+    ?assertEqual(ok, rpc:call(TwoPeer, pg, join, [?FUNCTION_NAME, ?FUNCTION_NAME, [RemotePid, RemotePid2]])),
+    sync({?FUNCTION_NAME, TwoPeer}),
+    ?assertEqual([RemotePid, RemotePid2], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+    %% now leave
+    ?assertEqual(ok, rpc:call(TwoPeer, pg, leave, [?FUNCTION_NAME, ?FUNCTION_NAME, [RemotePid2, RemotePid]])),
+    sync({?FUNCTION_NAME, TwoPeer}),
+    ?assertEqual([], pg:get_members(?FUNCTION_NAME, ?FUNCTION_NAME)),
+    {state, _, _, #{RemoteNode := {_, NewRemoteMap}}} = sys:get_state(?FUNCTION_NAME),
     stop_node(TwoPeer, Socket),
     ok.
 
-- 
2.26.2

openSUSE Build Service is sponsored by