Browse Source

added 'check_pids' option to gproc:table/2

Ulf Wiger 13 years ago
parent
commit
34a366b0af
3 changed files 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

+ 12 - 1
doc/gproc.md

@@ -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##
 
 

+ 57 - 30
src/gproc.erl

@@ -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;
-             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 ->
              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,
@@ -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(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_(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) ->
-    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 ->
 	true ->
 	    [];
 	    [];
 	false ->
 	false ->
@@ -2378,49 +2401,53 @@ qlc_lookup_pid(Pid, Scope) ->
     end.
     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
     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(Scope, K) ->
+qlc_prev(_, '$end_of_table', _) -> [];
+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
-    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;
 is_unique(n) -> true;

+ 26 - 9
test/gproc_tests.erl

@@ -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.