Skip to content

Commit

Permalink
added 'check_pids' option to gproc:table/2
Browse files Browse the repository at this point in the history
  • Loading branch information
uwiger authored and RJ committed Jul 9, 2012
1 parent 258cb24 commit 9b07765
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 40 deletions.
13 changes: 12 additions & 1 deletion doc/gproc.md
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1158,9 +1158,20 @@ Equivalent to [`table(Context, [])`](#table-2).<a name="table-2"></a>
<br></br> <br></br>






QLC table generator for the gproc registry. QLC table generator for the gproc registry.
Context specifies which subset of the registry should be queried. Context specifies which subset of the registry should be queried.
See [`http://www.erlang.org/doc/man/qlc.html`](http://www.erlang.org/doc/man/qlc.html).<a name="unreg-1"></a> See [`http://www.erlang.org/doc/man/qlc.html`](http://www.erlang.org/doc/man/qlc.html).

NOTE: By default, the gproc table generator will not filter out entries
belonging to processes that have just died, but which have yet to be cleared
out of the registry. Use the option `check_pids` (or `{check_pids, true}`)
if you want to filter out dead entries already in the query. There will be
some overhead associated with doing so, and given that the process monitoring
is asynchronous, there can never be any guarantee that there are no dead
entries in the list by the time your program processes it.
<a name="unreg-1"></a>


###unreg/1## ###unreg/1##


Expand Down
87 changes: 57 additions & 30 deletions src/gproc.erl
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2295,21 +2295,37 @@ table(Context) ->
%% @doc QLC table generator for the gproc registry. %% @doc QLC table generator for the gproc registry.
%% Context specifies which subset of the registry should be queried. %% Context specifies which subset of the registry should be queried.
%% See [http://www.erlang.org/doc/man/qlc.html]. %% See [http://www.erlang.org/doc/man/qlc.html].
%%
%% NOTE: By default, the gproc table generator will not filter out entries
%% belonging to processes that have just died, but which have yet to be cleared
%% out of the registry. Use the option `check_pids' (or `{check_pids, true}')
%% if you want to filter out dead entries already in the query. There will be
%% some overhead associated with doing so, and given that the process monitoring
%% is asynchronous, there can never be any guarantee that there are no dead
%% entries in the list by the time your program processes it.
%%
%% @end %% @end
table(Context, Opts) -> table(Context, Opts) ->
Ctxt = {_, Type} = get_s_t(Context), Ctxt = {_, Type} = get_s_t(Context),
[Traverse, NObjs] = [proplists:get_value(K,Opts,Def) || [Traverse, NObjs] = [proplists:get_value(K,Opts,Def) ||
{K,Def} <- [{traverse,select}, {n_objects,100}]], {K,Def} <- [{traverse,select}, {n_objects,100}]],
CheckPids = proplists:get_bool(check_pids, Opts),
TF = case Traverse of TF = case Traverse of
first_next -> first_next ->
fun() -> qlc_next(Ctxt, first(Ctxt)) end; fun() -> qlc_next(Ctxt, first(Ctxt), CheckPids) end;
last_prev -> fun() -> qlc_prev(Ctxt, last(Ctxt)) end; last_prev -> fun() -> qlc_prev(Ctxt, last(Ctxt), CheckPids) end;
select -> select ->
fun(MS) -> qlc_select( fun(MS) -> qlc_select(
select(Ctxt, wrap_qlc_ms_prod(MS), NObjs)) end; CheckPids,
select(Ctxt, wrap_qlc_ms_prod(CheckPids, MS),
NObjs))
end;
{select,MS} -> {select,MS} ->
fun() -> qlc_select( fun() -> qlc_select(
select(Ctxt, wrap_qlc_ms_prod(MS), NObjs)) end; CheckPids,
select(Ctxt, wrap_qlc_ms_prod(CheckPids, MS),
NObjs))
end;
_ -> _ ->
erlang:error(badarg, [Ctxt,Opts]) erlang:error(badarg, [Ctxt,Opts])
end, end,
Expand All @@ -2324,39 +2340,46 @@ table(Context, Opts) ->
LookupFun = LookupFun =
case Traverse of case Traverse of
{select, _MS} -> undefined; {select, _MS} -> undefined;
_ -> fun(Pos, Ks) -> qlc_lookup(Ctxt, Pos, Ks) end _ -> fun(Pos, Ks) -> qlc_lookup(Ctxt, Pos, Ks, CheckPids) end
end, end,
qlc:table(TF, [{info_fun, InfoFun}, qlc:table(TF, [{info_fun, InfoFun},
{lookup_fun, LookupFun}] ++ [{K,V} || {K,V} <- Opts, {lookup_fun, LookupFun}] ++ [{K,V} || {K,V} <- Opts,
K =/= traverse, K =/= traverse,
K =/= n_objects]). K =/= n_objects]).

wrap_qlc_ms_prod(false, Pats) ->
wrap_qlc_ms_prod(Pats) -> Pats;
wrap_qlc_ms_prod(true, Pats) ->
[ wrap_qlc_ms_prod_(P) || P <- Pats ]. [ wrap_qlc_ms_prod_(P) || P <- Pats ].


wrap_qlc_ms_prod_({H, Gs, [P]}) -> wrap_qlc_ms_prod_({H, Gs, [P]}) ->
{H, Gs, [{{ {element, 2, '$_'}, P }}]}. {H, Gs, [{{ {element, 2, '$_'}, P }}]}.


qlc_lookup(_Scope, 1, Keys) -> qlc_lookup(_Scope, 1, Keys, Check) ->
lists:flatmap( lists:flatmap(
fun(Key) -> fun(Key) ->
remove_dead( remove_dead(
Check,
ets:select(?TAB, [{ {{Key,'_'},'_','_'}, [], ets:select(?TAB, [{ {{Key,'_'},'_','_'}, [],
[{{ {element,1,{element,1,'$_'}}, [{{ {element,1,{element,1,'$_'}},
{element,2,'$_'}, {element,2,'$_'},
{element,3,'$_'} }}] }])) {element,3,'$_'} }}] }]))
end, Keys); end, Keys);
qlc_lookup(Scope, 2, Pids) -> qlc_lookup(Scope, 2, Pids, Check) ->
lists:flatmap(fun(Pid) -> lists:flatmap(fun(Pid) ->
qlc_lookup_pid(Pid, Scope) qlc_lookup_pid(Pid, Scope, Check)
end, Pids). end, Pids).


remove_dead(Objs) -> remove_dead(false, Objs) ->
Objs;
remove_dead(true, Objs) ->
[ Reg || {_, Pid, _} = Reg <- Objs, [ Reg || {_, Pid, _} = Reg <- Objs,
not ?PID_IS_DEAD(Pid) ]. not ?PID_IS_DEAD(Pid) ].


qlc_lookup_pid(Pid, Scope) -> %% While it may seem obsessive not to do the sensible pid liveness check here
case ?PID_IS_DEAD(Pid) of %% every time, we make it optional for consistency; this way, we can devise
%% a test case that verifies the difference between having the option and not.
qlc_lookup_pid(Pid, Scope, Check) ->
case Check andalso ?PID_IS_DEAD(Pid) of
true -> true ->
[]; [];
false -> false ->
Expand All @@ -2378,49 +2401,53 @@ qlc_lookup_pid(Pid, Scope) ->
end. end.




qlc_next(_, '$end_of_table') -> []; qlc_next(_, '$end_of_table', _) -> [];
qlc_next(Scope, K) -> qlc_next(Scope, K, Check) ->
case ets:lookup(?TAB, K) of case ets:lookup(?TAB, K) of
[{{Key,_}, Pid, V}] -> [{{Key,_}, Pid, V}] ->
case ?PID_IS_DEAD(Pid) of case Check andalso ?PID_IS_DEAD(Pid) of
true -> true ->
qlc_next(Scope, next(Scope, K)); qlc_next(Scope, next(Scope, K), Check);
false -> false ->
[{Key,Pid,V}] ++ fun() -> [{Key,Pid,V}] ++ fun() ->
qlc_next(Scope, next(Scope, K)) qlc_next(Scope, next(Scope, K),
Check)
end end
end; end;
[] -> [] ->
qlc_next(Scope, next(Scope, K)) qlc_next(Scope, next(Scope, K), Check)
end. end.


qlc_prev(_, '$end_of_table') -> []; qlc_prev(_, '$end_of_table', _) -> [];
qlc_prev(Scope, K) -> qlc_prev(Scope, K, Check) ->
case ets:lookup(?TAB, K) of case ets:lookup(?TAB, K) of
[{{Key,_},Pid,V}] -> [{{Key,_},Pid,V}] ->
case ?PID_IS_DEAD(Pid) of case Check andalso ?PID_IS_DEAD(Pid) of
true -> true ->
qlc_prev(Scope, prev(Scope, K)); qlc_prev(Scope, prev(Scope, K), Check);
false -> false ->
[{Key,Pid,V}] ++ fun() -> [{Key,Pid,V}] ++ fun() ->
qlc_prev(Scope, prev(Scope, K)) qlc_prev(Scope, prev(Scope, K),
Check)
end end
end; end;
[] -> [] ->
qlc_prev(Scope, prev(Scope, K)) qlc_prev(Scope, prev(Scope, K), Check)
end. end.


qlc_select('$end_of_table') -> qlc_select(_, '$end_of_table') ->
[]; [];
qlc_select({Objects, Cont}) -> qlc_select(true, {Objects, Cont}) ->
case [O || {Pid,O} <- Objects, case [O || {Pid,O} <- Objects,
not ?PID_IS_DEAD(Pid)] of not ?PID_IS_DEAD(Pid)] of
[] -> [] ->
%% re-run search %% re-run search
qlc_select(ets:select(Cont)); qlc_select(true, ets:select(Cont));
Found -> Found ->
Found ++ fun() -> qlc_select(ets:select(Cont)) end Found ++ fun() -> qlc_select(true, ets:select(Cont)) end
end. end;
qlc_select(false, {Objects, Cont}) ->
Objects ++ fun() -> qlc_select(false, ets:select(Cont)) end.




is_unique(n) -> true; is_unique(n) -> true;
Expand Down
35 changes: 26 additions & 9 deletions test/gproc_tests.erl
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -474,38 +474,55 @@ t_qlc_dead() ->
%% local names %% local names
Exp1 = [{{n,l,{n,1}},self(),x}], Exp1 = [{{n,l,{n,1}},self(),x}],
?assertEqual(Exp1, ?assertEqual(Exp1,
qlc:e(qlc:q([N || N <- gproc:table(names)]))), qlc:e(qlc:q([N || N <-
gproc:table(names, [check_pids])]))),
?assertEqual(Exp1, ?assertEqual(Exp1,
qlc:e(qlc:q([N || {{n,l,_},_,_} = N <- qlc:e(qlc:q([N || {{n,l,_},_,_} = N <-
gproc:table(names)]))), gproc:table(names, [check_pids])]))),
%% match local names on value %% match local names on value
Exp2 = [{{n,l,{n,1}},self(),x}], Exp2 = [{{n,l,{n,1}},self(),x}],
?assertEqual(Exp2, ?assertEqual(Exp2,
qlc:e(qlc:q([N || {{n,l,_},_,x} = N <- qlc:e(qlc:q([N || {{n,l,_},_,x} = N <-
gproc:table(names)]))), gproc:table(names, [check_pids])]))),
?assertEqual([], ?assertEqual([],
qlc:e(qlc:q([N || {{n,l,_},_,y} = N <- qlc:e(qlc:q([N || {{n,l,_},_,y} = N <-
gproc:table(names)]))), gproc:table(names, [check_pids])]))),
%% match all on value %% match all on value
Exp3 = [{{n,l,{n,1}},self(),x}, Exp3 = [{{n,l,{n,1}},self(),x},
{{p,l,{p,1}},self(),x}], {{p,l,{p,1}},self(),x}],
?assertEqual(Exp3, ?assertEqual(Exp3,
qlc:e(qlc:q([N || {_,_,x} = N <- gproc:table(all)]))), qlc:e(qlc:q([N || {_,_,x} = N <-
gproc:table(all, [check_pids])]))),
?assertEqual([], ?assertEqual([],
qlc:e(qlc:q([N || {_,_,y} = N <- gproc:table(all)]))), qlc:e(qlc:q([N || {_,_,y} = N <-
gproc:table(all, [check_pids])]))),
Exp3b = [{{n,l,{n,2}},P1,y},
{{p,l,{p,2}},P1,y}],
?assertEqual(Exp3b,
qlc:e(qlc:q([N || {_,_,y} = N <-
gproc:table(all)]))),


%% match all %% match all
Exp4 = [{{n,l,{n,1}},self(),x}, Exp4 = [{{n,l,{n,1}},self(),x},
{{p,l,{p,1}},self(),x}], {{p,l,{p,1}},self(),x}],
?assertEqual(Exp4, ?assertEqual(Exp4,
qlc:e(qlc:q([X || X <- gproc:table(all)]))), qlc:e(qlc:q([X || X <-
gproc:table(all, [check_pids])]))),
%% match on pid %% match on pid
?assertEqual(Exp4, ?assertEqual(Exp4,
qlc:e(qlc:q([{K,P,V} || {K,P,V} <- qlc:e(qlc:q([{K,P,V} || {K,P,V} <-
gproc:table(all), P =:= self()]))), gproc:table(all, [check_pids]),
P =:= self()]))),
?assertEqual([], ?assertEqual([],
qlc:e(qlc:q([{K,P,V} || {K,P,V} <- qlc:e(qlc:q([{K,P,V} || {K,P,V} <-
gproc:table(all), P =:= P1]))) gproc:table(all, [check_pids]),
P =:= P1]))),
Exp4b = [{{n,l,{n,2}},P1,y},
{{p,l,{p,2}},P1,y}],
?assertEqual(Exp4b,
qlc:e(qlc:q([{K,P,V} || {K,P,V} <-
gproc:table(all),
P =:= P1])))
after after
sys:resume(gproc) sys:resume(gproc)
end. end.
Expand Down

0 comments on commit 9b07765

Please sign in to comment.