Packages
otpbp
5.2.2
7.18.0
7.16.1
7.15.0
7.14.0
7.13.0
7.12.0
7.11.0
7.10.2
7.9.3
7.9.2
7.9.1
7.8.7
7.8.6
7.8.5
7.8.4
7.7.1
7.7.0
7.6.3
7.6.2
7.6.1
7.6.0
7.5.1
7.5.0
7.4.0
7.3.4
7.3.3
7.3.2
7.3.1
7.3.0
7.2.0
7.1.1
7.1.0
7.0.4
7.0.3
7.0.2
7.0.1
6.26.0
6.25.0
6.24.0
6.23.0
6.22.2
6.21.3
6.21.2
6.21.1
6.20.2
6.19.1
6.19.0
6.18.3
6.18.2
6.18.1
6.18.0
6.17.1
6.17.0
6.16.0
6.15.4
6.15.3
6.15.2
6.15.1
6.15.0
6.14.0
6.13.5
6.13.4
6.13.3
6.13.2
6.13.1
6.13.0
6.12.0
6.11.2
6.11.1
6.10.0
6.9.2
6.9.1
6.9.0
6.8.0
6.7.1
6.7.0
6.6.4
6.6.3
6.6.2
6.6.1
6.6.0
6.5.0
6.4.0
6.3.3
6.3.2
6.3.1
6.3.0
6.2.1
6.2.0
6.1.0
6.0.0
5.39.0
5.38.0
5.37.0
5.36.0
5.35.2
5.34.3
5.34.2
5.34.1
5.33.1
5.32.1
5.32.0
5.31.3
5.31.2
5.31.1
5.31.0
5.30.1
5.30.0
5.29.0
5.28.4
5.28.3
5.28.2
5.28.1
5.28.0
5.27.0
5.26.5
5.26.4
5.26.3
5.26.2
5.26.1
5.26.0
5.25.0
5.24.2
5.24.1
5.23.0
5.22.2
5.22.1
5.22.0
5.21.0
5.20.1
5.20.0
5.19.4
5.19.3
5.19.2
5.19.1
5.19.0
5.18.0
5.17.0
5.16.3
5.16.1
5.16.0
5.15.1
5.15.0
5.14.0
5.13.1
5.13.0
5.12.1
5.12.0
5.11.2
5.11.1
5.10.1
5.10.0
5.9.0
5.8.0
5.7.0
5.6.1
5.6.0
5.5.0
5.4.2
5.4.1
5.4.0
5.3.0
5.2.2
5.2.1
5.2.0
5.1.2
5.1.1
4.79.0
4.78.0
4.77.0
4.76.2
4.75.3
4.75.2
4.75.1
4.74.0
4.73.1
4.73.0
4.72.3
4.72.2
4.72.1
4.72.0
4.71.1
4.71.0
4.70.0
4.69.4
4.69.3
4.69.2
4.69.1
4.69.0
4.68.0
4.67.5
4.67.4
4.67.3
4.67.2
4.67.0
4.66.0
4.65.2
4.65.1
4.64.0
4.63.2
4.63.1
4.63.0
4.62.0
4.61.1
4.61.0
4.60.4
4.60.3
4.60.2
4.60.1
4.60.0
4.59.0
4.58.0
4.57.2
4.57.1
4.57.0
4.56.2
4.56.1
4.56.0
4.55.0
4.54.0
4.53.0
4.52.1
4.52.0
4.51.3
4.51.2
4.51.0
4.50.0
4.49.3
4.49.2
OTP backports
Current section
Files
Jump to
Current section
Files
src/otpbp_pg2.erl
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 1997-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
-module(otpbp_pg2).
-ifndef(HAVE_pg2__start_0).
-export([create/1,
delete/1,
join/2,
leave/2,
get_members/1,
get_local_members/1,
get_closest_pid/1,
which_groups/0]).
-export([start/0, start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
%%% As of R13B03 monitors are used instead of links.
%%%
%%% Exported functions
%%%
-define(SERVICE, pg2).
-spec start_link() -> {ok, pid()} | {error, any()}.
start_link() -> gen_server:start_link({local, ?SERVICE}, ?MODULE, [], []).
-spec start() -> {ok, pid()} | {error, any()}.
start() -> ensure_started().
-type name() :: any().
-spec create(Name :: name()) -> ok.
create(Name) ->
_ = ensure_started(),
ets:member(pg2_table, {group, Name}) orelse
global:trans({{?SERVICE, Name}, self()}, fun() -> gen_server:multi_call(?SERVICE, {create, Name}) end),
ok.
-spec delete(Name :: name()) -> ok.
delete(Name) ->
_ = ensure_started(),
global:trans({{?SERVICE, Name}, self()}, fun() -> gen_server:multi_call(?SERVICE, {delete, Name}) end),
ok.
-spec join(Name, Pid :: pid()) -> ok | {error, {no_such_group, Name}} when Name :: name().
join(Name, Pid) -> act(Name, Pid, join).
-spec leave(Name, Pid :: pid()) -> ok | {error, {no_such_group, Name}} when Name :: name().
leave(Name, Pid) -> act(Name, Pid, leave).
act(Name, Pid, Action) when is_pid(Pid) ->
_ = ensure_started(),
case ets:member(pg2_table, {group, Name}) of
false -> {error, {no_such_group, Name}};
true ->
global:trans({{?SERVICE, Name}, self()},
fun() -> gen_server:multi_call(?SERVICE, {Action, Name, Pid}) end),
ok
end.
-spec get_members(Name) -> [pid()] | {error, {no_such_group, Name}} when Name :: name().
get_members(Name) ->
_ = ensure_started(),
case ets:member(pg2_table, {group, Name}) of
true -> group_members(Name);
false -> {error, {no_such_group, Name}}
end.
-spec get_local_members(Name) -> [pid()] | {error, {no_such_group, Name}} when Name :: name().
get_local_members(Name) ->
_ = ensure_started(),
case ets:member(pg2_table, {group, Name}) of
true -> local_group_members(Name);
false -> {error, {no_such_group, Name}}
end.
-spec which_groups() -> [Name :: name()].
which_groups() ->
_ = ensure_started(),
all_groups().
-spec get_closest_pid(Name :: name()) -> pid() | {error, {no_process, Name} | {no_such_group, Name}}
when Name :: name().
get_closest_pid(Name) ->
case get_local_members(Name) of
[Pid] -> Pid;
[] ->
case get_members(Name) of
[] -> {error, {no_process, Name}};
Members -> random_element(Members)
end;
Members when is_list(Members) -> random_element(Members);
Else -> Else
end.
random_element(List) -> lists:nth(abs(erlang:monotonic_time() bxor erlang:unique_integer()) rem length(List) + 1, List).
%%%
%%% Callback functions from gen_server
%%%
-record(state, {}).
-type state() :: #state{}.
-spec init(Arg :: []) -> {ok, state()}.
init([]) ->
Ns = nodes(),
ok = net_kernel:monitor_nodes(true),
lists:foreach(fun(N) ->
{?SERVICE, N} ! {new_pg2, node()},
self() ! {nodeup, N}
end, Ns),
pg2_table = ets:new(pg2_table, [ordered_set, protected, named_table]),
{ok, #state{}}.
-spec handle_call({create, Name} | {delete, Name} | {join, Name, Pid :: pid()} | {leave, Name, Pid :: pid()},
From :: {pid(), Tag :: any()}, State :: state()) -> {reply, ok, state()}
when Name :: name().
handle_call({create, Name}, _From, S) ->
assure_group(Name),
{reply, ok, S};
handle_call({join, Name, Pid}, _From, S) ->
ets:member(pg2_table, {group, Name}) andalso join_group(Name, Pid),
{reply, ok, S};
handle_call({leave, Name, Pid}, _From, S) ->
ets:member(pg2_table, {group, Name}) andalso leave_group(Name, Pid),
{reply, ok, S};
handle_call({delete, Name}, _From, S) ->
delete_group(Name),
{reply, ok, S};
handle_call(Request, From, S) ->
error_logger:warning_msg("The pg2 server received an unexpected message:\n"
"handle_call(~tp, ~tp, _)\n",
[Request, From]),
{noreply, S}.
-spec handle_cast({exchange, node(), Names :: [[name(),...]]} | {del_member, name(), pid()},
State :: state()) -> {noreply, state()}.
handle_cast({exchange, _Node, Names}, S) ->
store(Names),
{noreply, S};
handle_cast(_, S) ->
%% Ignore {del_member, Name, Pid}.
{noreply, S}.
-spec handle_info(Tuple :: tuple(), State :: state()) -> {noreply, state()}.
handle_info({'DOWN', MonitorRef, process, _Pid, _Info}, S) ->
member_died(MonitorRef),
{noreply, S};
handle_info({T, Node}, S) when T =:= nodeup; T =:= new_pg2 ->
gen_server:cast({?SERVICE, Node}, {exchange, node(), all_members()}),
{noreply, S};
handle_info(_, S) -> {noreply, S}.
-spec terminate(Reason :: any(), State :: state()) -> ok.
terminate(_Reason, _S) ->
true = ets:delete(pg2_table),
ok.
%%%
%%% Local functions
%%%
-record(member, {name, pid}).
-record(local_member, {name, pid}).
-record(group, {name}).
-record(pid, {pid, name}).
-record(ref, {v}).
%%% One ETS table, pg2_table, is used for bookkeeping. The type of the
%%% table is ordered_set, and the fast matching of partially
%%% instantiated keys is used extensively.
%%%
%%% {#group{name = Name}}
%%% Process group Name.
%%% {#ref{v = Pid}, RPid, MonitorRef, Counter}
%%% {#ref{v = MonitorRef}, Pid}
%%% Each process has one monitor. Sometimes a process is spawned to
%%% monitor the pid (RPid). Counter is incremented when the Pid joins
%%% some group.
%%% {#member{name = Name, pid = Pid}, GroupCounter}
%%% {#local_member{name = Name, pid = Pid}}
%%% Pid is a member of group Name, GroupCounter is incremented when the
%%% Pid joins the group Name.
%%% {#pid{pid = Pid, name = Name}}
%%% Pid is a member of group Name.
store(List) ->
_ = [(assure_group(Name) andalso [join_group(Name, P) || P <- Members -- group_members(Name)]) || [Name, Members] <- List],
ok.
assure_group(Name) ->
Key = #group{name = Name},
ets:member(pg2_table, Key) orelse true =:= ets:insert(pg2_table, {Key}).
delete_group(Name) ->
lists:foreach(fun(Pid) -> leave_group(Name, Pid) end, group_members(Name)),
true = ets:delete(pg2_table, #group{name = Name}),
ok.
member_died(Ref) ->
[{#ref{}, Pid}] = ets:lookup(pg2_table, #ref{v = Ref}),
Names = member_groups(Pid),
_ = [leave_group(Name, P) || Name <- Names, P <- member_in_group(Pid, Name)],
%% Kept for backward compatibility with links. Can be removed, eventually.
lists:foreach(fun(Name) -> gen_server:abcast(nodes(), ?SERVICE, {del_member, Name, Pid}) end, Names),
ok.
join_group(Name, Pid) ->
Ref_Pid = #ref{v = Pid},
try ets:update_counter(pg2_table, Ref_Pid, {4, +1}) of
_ -> true
catch _:_ ->
{RPid, Ref} = do_monitor(Pid),
true = ets:insert(pg2_table, [{Ref_Pid, RPid, Ref, 1}, {#ref{v = Ref}, Pid}])
end,
Member = #member{name = Name, pid = Pid},
try
ets:update_counter(pg2_table, Member, {2, +1})
catch _:_ ->
true = ets:insert(pg2_table, [{Member, 1}, {#pid{pid = Pid, name = Name}}]),
node(Pid) =:= node() andalso ets:insert(pg2_table, {#local_member{name = Name, pid = Pid}})
end.
leave_group(Name, Pid) ->
Member = #member{name = Name, pid = Pid},
try ets:update_counter(pg2_table, Member, {2, -1}) of
N ->
ensure_delete_group(Name, Pid, Member, N),
Ref_Pid = #ref{v = Pid},
case ets:update_counter(pg2_table, Ref_Pid, {4, -1}) of
0 ->
[{#ref{}, RPid, Ref, 0}] = ets:lookup(pg2_table, Ref_Pid),
true = ets:delete(pg2_table, #ref{v = Ref}),
true = ets:delete(pg2_table, Ref_Pid),
true = erlang:demonitor(Ref, [flush]),
kill_monitor_proc(RPid, Pid);
_ -> ok
end
catch _:_ -> ok
end.
ensure_delete_group(_Name, _Pid, _Member, 0) -> true;
ensure_delete_group(Name, Pid, Member, _) ->
true = ets:delete(pg2_table, #pid{pid = Pid, name = Name}),
node(Pid) =:= node() andalso ets:delete(pg2_table, #local_member{name = Name, pid = Pid}),
true = ets:delete(pg2_table, Member).
all_members() -> [[G, group_members(G)] || G <- all_groups()].
group_members(Name) ->
[P || [P, N] <- ets:match(pg2_table, {#member{name = Name, pid = '$1'}, '$2'}), _ <- lists:seq(1, N)].
local_group_members(Name) ->
[P || [Pid] <- ets:match(pg2_table, {#local_member{name = Name, pid = '$1'}}), P <- member_in_group(Pid, Name)].
member_in_group(Pid, Name) ->
case ets:lookup(pg2_table, #member{name = Name, pid = Pid}) of
[] -> [];
[{#member{}, N}] -> lists:duplicate(N, Pid)
end.
member_groups(Pid) -> all_matches({#pid{pid = Pid, name = '$1'}}).
all_groups() -> all_matches({#group{name = '$1'}}).
all_matches(Spec) -> [N || [N] <- ets:match(pg2_table, Spec)].
ensure_started() ->
case whereis(?SERVICE) of
undefined ->
supervisor:start_child(kernel_safe_sup,
{pg2, {?MODULE, start_link, []}, permanent, 1000, worker, [?MODULE]});
Pg2Pid -> {ok, Pg2Pid}
end.
kill_monitor_proc(Pid, Pid) -> ok;
kill_monitor_proc(RPid, _Pid) -> exit(RPid, kill).
%% When/if erlang:monitor() returns before trying to connect to the
%% other node this function can be removed.
do_monitor(Pid) ->
case lists:member(node(Pid), nodes([this, visible])) of
true ->
%% Assume the node is still up
{Pid, monitor(process, Pid)};
false ->
spawn_monitor(fun() ->
Ref = monitor(process, Pid),
receive
{'DOWN', Ref, process, Pid, _Info} -> exit(normal)
end
end)
end.
-endif.