Permalink
Browse files

added 'check_pids' option to gproc:table/2

  • Loading branch information...
1 parent 258cb24 commit 9b07765029dcd408a090947b71ebed1f911b95d0 @uwiger uwiger committed with Jul 3, 2012
Showing with 95 additions and 40 deletions.
  1. +12 −1 doc/gproc.md
  2. +57 −30 src/gproc.erl
  3. +26 −9 test/gproc_tests.erl
View
@@ -1158,9 +1158,20 @@ Equivalent to [`table(Context, [])`](#table-2).<a name="table-2"></a>
<br></br>
+
+
QLC table generator for the gproc registry.
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##
View
@@ -2295,21 +2295,37 @@ table(Context) ->
%% @doc QLC table generator for the gproc registry.
%% Context specifies which subset of the registry should be queried.
%% 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
table(Context, Opts) ->
Ctxt = {_, Type} = get_s_t(Context),
[Traverse, NObjs] = [proplists:get_value(K,Opts,Def) ||
{K,Def} <- [{traverse,select}, {n_objects,100}]],
+ CheckPids = proplists:get_bool(check_pids, Opts),
TF = case Traverse of
first_next ->
- fun() -> qlc_next(Ctxt, first(Ctxt)) end;
- last_prev -> fun() -> qlc_prev(Ctxt, last(Ctxt)) end;
+ fun() -> qlc_next(Ctxt, first(Ctxt), CheckPids) end;
+ last_prev -> fun() -> qlc_prev(Ctxt, last(Ctxt), CheckPids) end;
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} ->
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])
end,
@@ -2324,39 +2340,46 @@ table(Context, Opts) ->
LookupFun =
case Traverse of
{select, _MS} -> undefined;
- _ -> fun(Pos, Ks) -> qlc_lookup(Ctxt, Pos, Ks) end
+ _ -> fun(Pos, Ks) -> qlc_lookup(Ctxt, Pos, Ks, CheckPids) end
end,
qlc:table(TF, [{info_fun, InfoFun},
{lookup_fun, LookupFun}] ++ [{K,V} || {K,V} <- Opts,
K =/= traverse,
K =/= n_objects]).
-
-wrap_qlc_ms_prod(Pats) ->
+wrap_qlc_ms_prod(false, Pats) ->
+ Pats;
+wrap_qlc_ms_prod(true, Pats) ->
[ wrap_qlc_ms_prod_(P) || P <- Pats ].
wrap_qlc_ms_prod_({H, Gs, [P]}) ->
{H, Gs, [{{ {element, 2, '$_'}, P }}]}.
-qlc_lookup(_Scope, 1, Keys) ->
+qlc_lookup(_Scope, 1, Keys, Check) ->
lists:flatmap(
fun(Key) ->
remove_dead(
+ Check,
ets:select(?TAB, [{ {{Key,'_'},'_','_'}, [],
[{{ {element,1,{element,1,'$_'}},
{element,2,'$_'},
{element,3,'$_'} }}] }]))
end, Keys);
-qlc_lookup(Scope, 2, Pids) ->
+qlc_lookup(Scope, 2, Pids, Check) ->
lists:flatmap(fun(Pid) ->
- qlc_lookup_pid(Pid, Scope)
+ qlc_lookup_pid(Pid, Scope, Check)
end, Pids).
-remove_dead(Objs) ->
+remove_dead(false, Objs) ->
+ Objs;
+remove_dead(true, Objs) ->
[ Reg || {_, Pid, _} = Reg <- Objs,
not ?PID_IS_DEAD(Pid) ].
-qlc_lookup_pid(Pid, Scope) ->
- case ?PID_IS_DEAD(Pid) of
+%% While it may seem obsessive not to do the sensible pid liveness check here
+%% 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 ->
[];
false ->
@@ -2378,49 +2401,53 @@ qlc_lookup_pid(Pid, Scope) ->
end.
-qlc_next(_, '$end_of_table') -> [];
-qlc_next(Scope, K) ->
+qlc_next(_, '$end_of_table', _) -> [];
+qlc_next(Scope, K, Check) ->
case ets:lookup(?TAB, K) of
[{{Key,_}, Pid, V}] ->
- case ?PID_IS_DEAD(Pid) of
+ case Check andalso ?PID_IS_DEAD(Pid) of
true ->
- qlc_next(Scope, next(Scope, K));
+ qlc_next(Scope, next(Scope, K), Check);
false ->
[{Key,Pid,V}] ++ fun() ->
- qlc_next(Scope, next(Scope, K))
+ qlc_next(Scope, next(Scope, K),
+ Check)
end
end;
[] ->
- qlc_next(Scope, next(Scope, K))
+ qlc_next(Scope, next(Scope, K), Check)
end.
-qlc_prev(_, '$end_of_table') -> [];
-qlc_prev(Scope, K) ->
+qlc_prev(_, '$end_of_table', _) -> [];
+qlc_prev(Scope, K, Check) ->
case ets:lookup(?TAB, K) of
[{{Key,_},Pid,V}] ->
- case ?PID_IS_DEAD(Pid) of
+ case Check andalso ?PID_IS_DEAD(Pid) of
true ->
- qlc_prev(Scope, prev(Scope, K));
+ qlc_prev(Scope, prev(Scope, K), Check);
false ->
[{Key,Pid,V}] ++ fun() ->
- qlc_prev(Scope, prev(Scope, K))
+ qlc_prev(Scope, prev(Scope, K),
+ Check)
end
end;
[] ->
- qlc_prev(Scope, prev(Scope, K))
+ qlc_prev(Scope, prev(Scope, K), Check)
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,
not ?PID_IS_DEAD(Pid)] of
[] ->
%% re-run search
- qlc_select(ets:select(Cont));
+ qlc_select(true, ets:select(Cont));
Found ->
- Found ++ fun() -> qlc_select(ets:select(Cont)) end
- end.
+ Found ++ fun() -> qlc_select(true, ets:select(Cont)) end
+ end;
+qlc_select(false, {Objects, Cont}) ->
+ Objects ++ fun() -> qlc_select(false, ets:select(Cont)) end.
is_unique(n) -> true;
View
@@ -474,38 +474,55 @@ t_qlc_dead() ->
%% local names
Exp1 = [{{n,l,{n,1}},self(),x}],
?assertEqual(Exp1,
- qlc:e(qlc:q([N || N <- gproc:table(names)]))),
+ qlc:e(qlc:q([N || N <-
+ gproc:table(names, [check_pids])]))),
?assertEqual(Exp1,
qlc:e(qlc:q([N || {{n,l,_},_,_} = N <-
- gproc:table(names)]))),
+ gproc:table(names, [check_pids])]))),
%% match local names on value
Exp2 = [{{n,l,{n,1}},self(),x}],
?assertEqual(Exp2,
qlc:e(qlc:q([N || {{n,l,_},_,x} = N <-
- gproc:table(names)]))),
+ gproc:table(names, [check_pids])]))),
?assertEqual([],
qlc:e(qlc:q([N || {{n,l,_},_,y} = N <-
- gproc:table(names)]))),
+ gproc:table(names, [check_pids])]))),
%% match all on value
Exp3 = [{{n,l,{n,1}},self(),x},
{{p,l,{p,1}},self(),x}],
?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([],
- 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
Exp4 = [{{n,l,{n,1}},self(),x},
{{p,l,{p,1}},self(),x}],
?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
?assertEqual(Exp4,
qlc:e(qlc:q([{K,P,V} || {K,P,V} <-
- gproc:table(all), P =:= self()]))),
+ gproc:table(all, [check_pids]),
+ P =:= self()]))),
?assertEqual([],
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
sys:resume(gproc)
end.

0 comments on commit 9b07765

Please sign in to comment.