Current section
Files
Jump to
Current section
Files
src/cpg_data.erl
%-*-Mode:erlang;coding:utf-8;tab-width:4;c-basic-offset:4;indent-tabs-mode:()-*-
% ex: set ft=erlang fenc=utf-8 sts=4 ts=4 sw=4 et nomod:
%%%
%%%------------------------------------------------------------------------
%%% @doc
%%% ==CPG Data Group Lookups.==
%%% All functions in this module are for using cached cpg data without
%%% sending messages to the cpg scope process. Using this module for
%%% cpg group lookups is equivalent to the lazy destination refresh
%%% methods in CloudI.
%%% @end
%%%
%%% MIT License
%%%
%%% Copyright (c) 2011-2018 Michael Truog <mjtruog at gmail dot com>
%%%
%%% Permission is hereby granted, free of charge, to any person obtaining a
%%% copy of this software and associated documentation files (the "Software"),
%%% to deal in the Software without restriction, including without limitation
%%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
%%% and/or sell copies of the Software, and to permit persons to whom the
%%% Software is furnished to do so, subject to the following conditions:
%%%
%%% The above copyright notice and this permission notice shall be included in
%%% all copies or substantial portions of the Software.
%%%
%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
%%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
%%% DEALINGS IN THE SOFTWARE.
%%%
%%% @author Michael Truog <mjtruog [at] gmail (dot) com>
%%% @copyright 2011-2018 Michael Truog
%%% @version 1.7.3 {@date} {@time}
%%%------------------------------------------------------------------------
-module(cpg_data).
-author('mjtruog [at] gmail (dot) com').
-export([get_groups/0,
get_groups/1,
get_groups/2,
get_groups/3,
get_empty_groups/0,
get_members/2,
get_members/3,
get_local_members/2,
get_local_members/3,
get_remote_members/2,
get_remote_members/3,
which_groups/1,
get_closest_pid/2,
get_closest_pid/3,
get_furthest_pid/2,
get_furthest_pid/3,
get_random_pid/2,
get_random_pid/3,
get_local_pid/2,
get_local_pid/3,
get_remote_pid/2,
get_remote_pid/3,
get_oldest_pid/2,
get_oldest_pid/3,
get_local_oldest_pid/2,
get_local_oldest_pid/3,
get_remote_oldest_pid/2,
get_remote_oldest_pid/3,
get_newest_pid/2,
get_newest_pid/3,
get_local_newest_pid/2,
get_local_newest_pid/3,
get_remote_newest_pid/2,
get_remote_newest_pid/3]).
-include("cpg_data.hrl").
-include("cpg_constants.hrl").
% GroupsData == GroupName -> #cpg_data{} lookup, using the DictI module
-type state() :: {DictI :: module(), GroupsData :: any()}.
-export_type([state/0]).
-type get_members_return() ::
{ok, cpg:name(), nonempty_list(pid())} |
{error, {no_such_group, cpg:name()}}.
-type get_pid_error_reason() ::
{no_process, cpg:name()} |
{no_such_group, cpg:name()}.
-export_type([get_members_return/0,
get_pid_error_reason/0]).
%%%------------------------------------------------------------------------
%%% External interface functions
%%%------------------------------------------------------------------------
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the group storage.===
%% This provides the internal representation of process groups so that
%% requests will not be blocked by the single process managing the scope
%% of the process groups.
%% @end
%%-------------------------------------------------------------------------
-spec get_groups() ->
state().
get_groups() ->
gen_server:call(?DEFAULT_SCOPE, cpg_data).
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the group storage for a particular scope or after a period of time.===
%% This provides the internal representation of process groups so that
%% requests will not be blocked by the single process managing the scope
%% of the process groups.
%% @end
%%-------------------------------------------------------------------------
-spec get_groups(atom() | non_neg_integer()) ->
state() | reference().
get_groups(Scope) when is_atom(Scope) ->
gen_server:call(Scope, cpg_data);
% send the groups as {cpg_data, Groups} after Time milliseconds to self()
get_groups(Time) when is_integer(Time) ->
erlang:send_after(Time, ?DEFAULT_SCOPE, {cpg_data, self()}).
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the group storage for a particular scope after a period of time.===
%% This provides the internal representation of process groups so that
%% requests will not be blocked by the single process managing the scope
%% of the process groups.
%% @end
%%-------------------------------------------------------------------------
-spec get_groups(Scope :: atom(),
Time :: non_neg_integer()) ->
reference().
get_groups(Scope, Time) when is_atom(Scope), is_integer(Time) ->
erlang:send_after(Time, Scope, {cpg_data, self()}).
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the group storage for a particular scope and destination after a period of time.===
%% This provides the internal representation of process groups so that
%% requests will not be blocked by the single process managing the scope
%% of the process groups.
%% @end
%%-------------------------------------------------------------------------
-spec get_groups(Scope :: atom(),
Destination :: pid() | atom(),
Time :: non_neg_integer()) ->
reference().
get_groups(Scope, Destination, Time)
when is_atom(Scope), (is_pid(Destination) orelse is_atom(Destination)),
is_integer(Time) ->
erlang:send_after(Time, Scope, {cpg_data, Destination}).
%%-------------------------------------------------------------------------
%% @doc
%% ===Get empty group storage.===
%% @end
%%-------------------------------------------------------------------------
-spec get_empty_groups() ->
state().
get_empty_groups() ->
DictI = cpg_app:group_storage(),
{DictI, DictI:new()}.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the members of a specific group.===
%% All members are ordered from newest to oldest, based on the group
%% membership surviving netsplits (join order, not pid creation time).
%% @end
%%-------------------------------------------------------------------------
-spec get_members(GroupName :: cpg:name(),
Groups :: state()) ->
get_members_return().
get_members(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
{ok, Pattern, History}
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the members of a specific group while excluding a specific pid.===
%% All members are ordered from newest to oldest, based on the group
%% membership surviving netsplits (join order, not pid creation time).
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_members(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
get_members_return().
get_members(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
Members = [Pid || Pid <- History,
Pid =/= Exclude],
if
Members == [] ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
true ->
{ok, Pattern, Members}
end
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get only the local members of a specific group.===
%% All members are ordered from newest to oldest, based on the
%% join order, not pid creation time.
%% @end
%%-------------------------------------------------------------------------
-spec get_local_members(GroupName :: cpg:name(),
Groups :: state()) ->
get_members_return().
get_local_members(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
{ok, Pattern, #cpg_data{local = Local}} ->
{ok, Pattern, Local}
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get only the local members of a specific group while excluding a specific pid.===
%% All members are ordered from newest to oldest, based on the
%% join order, not pid creation time.
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_local_members(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
get_members_return().
get_local_members(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
{ok, Pattern, #cpg_data{local = Local}} ->
Members = [Pid || Pid <- Local, Pid =/= Exclude],
if
Members == [] ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
true ->
{ok, Pattern, Members}
end
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get only the remote members of a specific group.===
%% All members are ordered from newest to oldest, based on the group
%% membership surviving netsplits (join order, not pid creation time).
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_members(GroupName :: cpg:name(),
Groups :: state()) ->
get_members_return().
get_remote_members(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
{ok, Pattern, #cpg_data{remote = Remote}} ->
{ok, Pattern, Remote}
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get only the remote members of a specific group while excluding a specific pid.===
%% All members are ordered from newest to oldest, based on the group
%% membership surviving netsplits (join order, not pid creation time).
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_members(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
get_members_return().
get_remote_members(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
{ok, Pattern, #cpg_data{remote = Remote}} ->
Members = [Pid || Pid <- Remote, Pid =/= Exclude],
if
Members == [] ->
% to keep return values consistent with pg2
{error, {'no_such_group', GroupName}};
true ->
{ok, Pattern, Members}
end
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get all the groups currently defined.===
%% @end
%%-------------------------------------------------------------------------
-spec which_groups(state()) ->
list(cpg:name()).
which_groups({DictI, GroupsData}) ->
DictI:fetch_keys(GroupsData).
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a group member, with local pids given priority.===
%% @end
%%-------------------------------------------------------------------------
-spec get_closest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_closest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{local_count = 0,
remote_count = RemoteCount,
remote = Remote}} ->
pick(RemoteCount, Remote, Pattern);
{ok, Pattern, #cpg_data{local_count = LocalCount,
local = Local}} ->
pick(LocalCount, Local, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a group member, with local pids given priority while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_closest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_closest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{local_count = 0,
remote_count = RemoteCount,
remote = Remote}} ->
pick(RemoteCount, Remote,
Exclude, GroupName, Pattern);
{ok, Pattern, #cpg_data{local_count = LocalCount,
local = Local,
remote_count = RemoteCount,
remote = Remote}} ->
pick(LocalCount, Local, RemoteCount, Remote,
Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a group member, with remote pids given priority.===
%% @end
%%-------------------------------------------------------------------------
-spec get_furthest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_furthest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{remote_count = 0,
local_count = LocalCount,
local = Local}} ->
pick(LocalCount, Local, Pattern);
{ok, Pattern, #cpg_data{remote_count = RemoteCount,
remote = Remote}} ->
pick(RemoteCount, Remote, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a group member, with remote pids given priority while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_furthest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_furthest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{remote_count = 0,
local_count = LocalCount,
local = Local}} ->
pick(LocalCount, Local, Exclude, GroupName, Pattern);
{ok, Pattern, #cpg_data{remote_count = RemoteCount,
remote = Remote,
local_count = LocalCount,
local = Local}} ->
pick(RemoteCount, Remote, LocalCount, Local,
Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_random_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_random_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{local_count = LocalCount,
local = Local,
remote_count = RemoteCount,
remote = Remote}} ->
pick(LocalCount + RemoteCount, Local ++ Remote, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_random_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_random_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{local_count = LocalCount,
local = Local,
remote_count = RemoteCount,
remote = Remote}} ->
pick(LocalCount + RemoteCount, Local ++ Remote,
Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a local group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_local_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_local_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{local_count = LocalCount,
local = Local}} ->
pick(LocalCount, Local, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a local group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_local_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_local_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{local_count = LocalCount,
local = Local}} ->
pick(LocalCount, Local, Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a remote group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_remote_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{remote_count = RemoteCount,
remote = Remote}} ->
pick(RemoteCount, Remote, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get a remote group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_remote_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{remote_count = RemoteCount,
remote = Remote}} ->
pick(RemoteCount, Remote, Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the oldest group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_oldest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_oldest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_oldest(History, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the oldest group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_oldest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_oldest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_oldest(History, Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the oldest local group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_local_oldest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_local_oldest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_local_oldest(History, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the oldest local group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_local_oldest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_local_oldest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_local_oldest(History, Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the oldest remote group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_oldest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_remote_oldest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_remote_oldest(History, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the oldest remote group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_oldest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_remote_oldest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_remote_oldest(History, Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the newest group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_newest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_newest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_newest(History, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the newest group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_newest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_newest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{history = []}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_newest(History, Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the newest local group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_local_newest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_local_newest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_local_newest(History, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the newest local group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_local_newest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_local_newest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{local_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_local_newest(History, Exclude, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the newest remote group member.===
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_newest_pid(GroupName :: cpg:name(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_remote_newest_pid(GroupName, Groups) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_remote_newest(History, GroupName, Pattern)
end.
%%-------------------------------------------------------------------------
%% @doc
%% ===Get the newest remote group member while excluding a specific pid.===
%% Usually the self() pid is excluded with this function call.
%% @end
%%-------------------------------------------------------------------------
-spec get_remote_newest_pid(GroupName :: cpg:name(),
Exclude :: pid(),
Groups :: state()) ->
{ok, cpg:name(), pid()} |
{error, get_pid_error_reason()}.
get_remote_newest_pid(GroupName, Exclude, Groups)
when is_pid(Exclude) ->
case group_find(GroupName, Groups) of
error ->
{error, {'no_such_group', GroupName}};
{ok, _, #cpg_data{remote_count = 0}} ->
{error, {'no_process', GroupName}};
{ok, Pattern, #cpg_data{history = History}} ->
history_remote_newest(History, Exclude, GroupName, Pattern)
end.
%%%------------------------------------------------------------------------
%%% Private functions
%%%------------------------------------------------------------------------
% should names be matched with "*" interpreted as a wildcard within the
% trie holding the groups of processes
% matching with patterns
group_find(GroupName, {trie, GroupsData}) ->
try trie:find_match(GroupName, GroupsData) catch
exit:badarg ->
error
end;
% matching without patterns
group_find(GroupName, {DictI, GroupsData}) ->
case DictI:find(GroupName, GroupsData) of
{ok, Value} ->
{ok, GroupName, Value};
error ->
error
end.
pick(1, [Pid], Pattern) ->
{ok, Pattern, Pid};
pick(N, L, Pattern) ->
Pid = lists:nth(random(N), L),
{ok, Pattern, Pid}.
pick_i(I, I, _, [], [], _, GroupName, _) ->
{error, {'no_process', GroupName}};
pick_i(I, I, 1, [Pid], [], _, _, Pattern) ->
{ok, Pattern, Pid};
pick_i(I, I, Length, Filtered, [], _, _, Pattern) ->
{ok, Pattern, lists:nth((I rem Length) + 1, Filtered)};
pick_i(I, I, Length, Filtered,
[Exclude | L], Exclude, GroupName, Pattern) ->
pick_i(I, I, Length, Filtered, L, Exclude, GroupName, Pattern);
pick_i(I, I, _, _, [Pid | _], _, _, Pattern) ->
{ok, Pattern, Pid};
pick_i(I, Random, Length, Filtered,
[Exclude | L], Exclude, GroupName, Pattern) ->
pick_i(I + 1, Random, Length, Filtered, L, Exclude, GroupName, Pattern);
pick_i(I, Random, Length, Filtered,
[Pid | L], Exclude, GroupName, Pattern) ->
pick_i(I + 1, Random, Length + 1, [Pid | Filtered],
L, Exclude, GroupName, Pattern).
pick(0, [], _, GroupName, _) ->
{error, {'no_process', GroupName}};
pick(N, L, Exclude, GroupName, Pattern) ->
pick_i(1, random(N), 0, [], L, Exclude, GroupName, Pattern).
pick(0, [], N2, L2, Exclude, GroupName, Pattern) ->
pick(N2, L2, Exclude, GroupName, Pattern);
pick(N1, L1, N2, L2, Exclude, GroupName, Pattern) ->
case pick(N1, L1, Exclude, GroupName, Pattern) of
{error, _} ->
pick(N2, L2, Exclude, GroupName, Pattern);
{ok, _, _} = Success ->
Success
end.
history_oldest([_ | _] = History, Pattern) ->
[Pid | _] = lists:reverse(History),
{ok, Pattern, Pid}.
history_oldest_pid([], _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_oldest_pid([Exclude | History], Exclude, GroupName, Pattern) ->
history_oldest_pid(History, Exclude, GroupName, Pattern);
history_oldest_pid([Pid | _], _, _, Pattern) ->
{ok, Pattern, Pid}.
history_oldest([_ | _] = History, Exclude, GroupName, Pattern) ->
history_oldest_pid(lists:reverse(History), Exclude, GroupName, Pattern).
history_local_oldest_pid([], _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_local_oldest_pid([Pid | _], Node, _, Pattern)
when node(Pid) =:= Node ->
{ok, Pattern, Pid};
history_local_oldest_pid([_ | History], Node, GroupName, Pattern) ->
history_local_oldest_pid(History, Node, GroupName, Pattern).
history_local_oldest([_ | _] = History, GroupName, Pattern) ->
history_local_oldest_pid(lists:reverse(History), node(),
GroupName, Pattern).
history_local_oldest_pid([], _, _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_local_oldest_pid([Exclude | History], Exclude, Node,
GroupName, Pattern) ->
history_local_oldest_pid(History, Exclude, Node, GroupName, Pattern);
history_local_oldest_pid([Pid | _], _, Node, _, Pattern)
when node(Pid) =:= Node ->
{ok, Pattern, Pid};
history_local_oldest_pid([_ | History], Exclude, Node, GroupName, Pattern) ->
history_local_oldest_pid(History, Exclude, Node, GroupName, Pattern).
history_local_oldest([_ | _] = History, Exclude, GroupName, Pattern) ->
history_local_oldest_pid(lists:reverse(History), Exclude, node(),
GroupName, Pattern).
history_remote_oldest_pid([], _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_remote_oldest_pid([Pid | _], Node, _, Pattern)
when node(Pid) =/= Node ->
{ok, Pattern, Pid};
history_remote_oldest_pid([_ | History], Node, GroupName, Pattern) ->
history_remote_oldest_pid(History, Node, GroupName, Pattern).
history_remote_oldest([_ | _] = History, GroupName, Pattern) ->
history_remote_oldest_pid(lists:reverse(History), node(),
GroupName, Pattern).
history_remote_oldest_pid([], _, _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_remote_oldest_pid([Exclude | History], Exclude, Node,
GroupName, Pattern) ->
history_remote_oldest_pid(History, Exclude, Node, GroupName, Pattern);
history_remote_oldest_pid([Pid | _], _, Node, _, Pattern)
when node(Pid) =/= Node ->
{ok, Pattern, Pid};
history_remote_oldest_pid([_ | History], Exclude, Node, GroupName, Pattern) ->
history_remote_oldest_pid(History, Exclude, Node, GroupName, Pattern).
history_remote_oldest([_ | _] = History, Exclude, GroupName, Pattern) ->
history_remote_oldest_pid(lists:reverse(History), Exclude, node(),
GroupName, Pattern).
history_newest([Pid | _], Pattern) ->
{ok, Pattern, Pid}.
history_newest_pid([], _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_newest_pid([Exclude | History], Exclude, GroupName, Pattern) ->
history_newest_pid(History, Exclude, GroupName, Pattern);
history_newest_pid([Pid | _], _, _, Pattern) ->
{ok, Pattern, Pid}.
history_newest([_ | _] = History, Exclude, GroupName, Pattern) ->
history_newest_pid(History, Exclude, GroupName, Pattern).
history_local_newest_pid([], _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_local_newest_pid([Pid | _], Node, _, Pattern)
when node(Pid) =:= Node ->
{ok, Pattern, Pid};
history_local_newest_pid([_ | History], Node, GroupName, Pattern) ->
history_local_newest_pid(History, Node, GroupName, Pattern).
history_local_newest([_ | _] = History, GroupName, Pattern) ->
history_local_newest_pid(History, node(), GroupName, Pattern).
history_local_newest_pid([], _, _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_local_newest_pid([Exclude | History], Exclude, Node,
GroupName, Pattern) ->
history_local_newest_pid(History, Exclude, Node, GroupName, Pattern);
history_local_newest_pid([Pid | _], _, Node, _, Pattern)
when node(Pid) =:= Node ->
{ok, Pattern, Pid};
history_local_newest_pid([_ | History], Exclude, Node, GroupName, Pattern) ->
history_local_newest_pid(History, Exclude, Node, GroupName, Pattern).
history_local_newest([_ | _] = History, Exclude, GroupName, Pattern) ->
history_local_newest_pid(History, Exclude, node(), GroupName, Pattern).
history_remote_newest_pid([], _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_remote_newest_pid([Pid | _], Node, _, Pattern)
when node(Pid) =/= Node ->
{ok, Pattern, Pid};
history_remote_newest_pid([_ | History], Node, GroupName, Pattern) ->
history_remote_newest_pid(History, Node, GroupName, Pattern).
history_remote_newest([_ | _] = History, GroupName, Pattern) ->
history_remote_newest_pid(History, node(), GroupName, Pattern).
history_remote_newest_pid([], _, _, GroupName, _) ->
{error, {'no_process', GroupName}};
history_remote_newest_pid([Exclude | History], Exclude, Node,
GroupName, Pattern) ->
history_remote_newest_pid(History, Exclude, Node, GroupName, Pattern);
history_remote_newest_pid([Pid | _], _, Node, _, Pattern)
when node(Pid) =/= Node ->
{ok, Pattern, Pid};
history_remote_newest_pid([_ | History], Exclude, Node, GroupName, Pattern) ->
history_remote_newest_pid(History, Exclude, Node, GroupName, Pattern).
history_remote_newest([_ | _] = History, Exclude, GroupName, Pattern) ->
history_remote_newest_pid(History, Exclude, node(), GroupName, Pattern).
-compile({inline, [{random,1}]}).
random(N) ->
quickrand:uniform(N).