gproc_lib.erl 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. %% ``The contents of this file are subject to the Erlang Public License,
  2. %% Version 1.1, (the "License"); you may not use this file except in
  3. %% compliance with the License. You should have received a copy of the
  4. %% Erlang Public License along with this software. If not, it can be
  5. %% retrieved via the world wide web at http://www.erlang.org/.
  6. %%
  7. %% Software distributed under the License is distributed on an "AS IS"
  8. %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  9. %% the License for the specific language governing rights and limitations
  10. %% under the License.
  11. %%
  12. %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
  13. %% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
  14. %% AB. All Rights Reserved.''
  15. %%
  16. %% @author Ulf Wiger <ulf@wiger.net>
  17. %%
  18. %% @doc Extended process registry
  19. %% <p>This module implements an extended process registry</p>
  20. %% <p>For a detailed description, see gproc/doc/erlang07-wiger.pdf.</p>
  21. %% @end
  22. -module(gproc_lib).
  23. -export([await/3,
  24. do_set_counter_value/3,
  25. do_set_value/3,
  26. ensure_monitor/2,
  27. insert_many/4,
  28. insert_reg/4, insert_reg/5,
  29. insert_attr/4,
  30. remove_many/4,
  31. remove_reg/3, remove_reg/4,
  32. monitors/1,
  33. standbys/1,
  34. followers/1,
  35. remove_monitor_pid/2,
  36. add_monitor/4,
  37. remove_monitor/3,
  38. remove_monitors/3,
  39. remove_reverse_mapping/3, remove_reverse_mapping/4,
  40. notify/2, notify/3,
  41. remove_wait/4,
  42. update_aggr_counter/3,
  43. update_counter/3,
  44. decrement_resource_count/2,
  45. valid_opts/2]).
  46. -export([dbg/1]).
  47. -include("gproc_int.hrl").
  48. -include("gproc.hrl").
  49. dbg(Mods) ->
  50. dbg:tracer(),
  51. [dbg:tpl(M,x) || M <- Mods],
  52. dbg:tp(ets,'_',[{[gproc,'_'], [], [{message,{exception_trace}}]}]),
  53. dbg:p(all,[c]).
  54. %% We want to store names and aggregated counters with the same
  55. %% structure as properties, but at the same time, we must ensure
  56. %% that the key is unique. We replace the Pid in the key part
  57. %% with an atom. To know which Pid owns the object, we lug the
  58. %% Pid around as payload as well. This is a bit redundant, but
  59. %% symmetric.
  60. %%
  61. -spec insert_reg(gproc:key(), any(), pid() | shared, gproc:scope()) -> boolean().
  62. insert_reg(K, Value, Pid, Scope) ->
  63. insert_reg(K, Value, Pid, Scope, registered).
  64. insert_reg({T,_,Name} = K, Value, Pid, Scope, Event) when T==a; T==n; T==rc ->
  65. Res = case ets:insert_new(?TAB, {{K,T}, Pid, Value}) of
  66. true ->
  67. %% Use insert_new to avoid overwriting existing entry
  68. _ = ets:insert_new(?TAB, {{Pid,K}, []}),
  69. true;
  70. false ->
  71. maybe_waiters(K, Pid, Value, T, Event)
  72. end,
  73. maybe_scan(T, Pid, Scope, Name, K),
  74. Res;
  75. insert_reg({p,Scope,_} = K, Value, shared, Scope, _E)
  76. when Scope == g; Scope == l ->
  77. %% shared properties are unique
  78. Info = [{{K, shared}, shared, Value}, {{shared,K}, []}],
  79. ets:insert_new(?TAB, Info);
  80. insert_reg({c,Scope,Ctr} = Key, Value, Pid, Scope, _E) when Scope==l; Scope==g ->
  81. %% Non-unique keys; store Pid in the key part
  82. K = {Key, Pid},
  83. Kr = {Pid, Key},
  84. Res = ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, [{initial, Value}]}]),
  85. case Res of
  86. true ->
  87. update_aggr_counter(Scope, Ctr, Value);
  88. false ->
  89. ignore
  90. end,
  91. Res;
  92. insert_reg({r,Scope,R} = Key, Value, Pid, Scope, _E) when Scope==l; Scope==g ->
  93. K = {Key, Pid},
  94. Kr = {Pid, Key},
  95. Res = ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, [{initial, Value}]}]),
  96. case Res of
  97. true ->
  98. update_resource_count(Scope, R, 1);
  99. false ->
  100. ignore
  101. end,
  102. Res;
  103. insert_reg({_,_,_} = Key, Value, Pid, _Scope, _E) when is_pid(Pid) ->
  104. %% Non-unique keys; store Pid in the key part
  105. K = {Key, Pid},
  106. Kr = {Pid, Key},
  107. ets:insert_new(?TAB, [{K, Pid, Value}, {Kr, []}]).
  108. maybe_scan(a, Pid, Scope, Name, K) ->
  109. Initial = scan_existing_counters(Scope, Name),
  110. ets:insert(?TAB, {{K,a}, Pid, Initial});
  111. maybe_scan(rc, Pid, Scope, Name, K) ->
  112. Initial = scan_existing_resources(Scope, Name),
  113. ets:insert(?TAB, {{K,rc}, Pid, Initial});
  114. maybe_scan(_, _, _, _, _) ->
  115. true.
  116. insert_attr({_,Scope,_} = Key, Attrs, Pid, Scope) when Scope==l;
  117. Scope==g ->
  118. case ets:lookup(?TAB, K = {Pid, Key}) of
  119. [{_, Attrs0}] when is_list(Attrs) ->
  120. As = proplists:get_value(attrs, Attrs0, []),
  121. As1 = lists:foldl(fun({K1,_} = Attr, Acc) ->
  122. lists:keystore(K1, 1, Acc, Attr)
  123. end, As, Attrs),
  124. Attrs1 = lists:keystore(attrs, 1, Attrs0, {attrs, As1}),
  125. ets:insert(?TAB, {K, Attrs1}),
  126. Attrs1;
  127. _ ->
  128. false
  129. end.
  130. get_attr(Attr, Pid, {_,_,_} = Key, Default) ->
  131. case ets:lookup(?TAB, {Pid, Key}) of
  132. [{_, Opts}] when is_list(Opts) ->
  133. case lists:keyfind(attrs, 1, Opts) of
  134. {_, Attrs} ->
  135. case lists:keyfind(Attr, 1, Attrs) of
  136. {_, Val} ->
  137. Val;
  138. _ ->
  139. Default
  140. end;
  141. _ ->
  142. Default
  143. end;
  144. _ ->
  145. Default
  146. end.
  147. -spec insert_many(gproc:type(), gproc:scope(), [{gproc:key(),any()}], pid()) ->
  148. {true,list()} | false.
  149. insert_many(T, Scope, KVL, Pid) ->
  150. Objs = mk_reg_objs(T, Scope, Pid, KVL),
  151. case ets:insert_new(?TAB, Objs) of
  152. true ->
  153. RevObjs = mk_reg_rev_objs(T, Scope, Pid, KVL),
  154. ets:insert(?TAB, RevObjs),
  155. _ = gproc_lib:ensure_monitor(Pid, Scope),
  156. {true, Objs};
  157. false ->
  158. Existing = [{Obj, ets:lookup(?TAB, K)} || {K,_,_} = Obj <- Objs],
  159. case lists:any(fun({_, [{_, _, _}]}) ->
  160. true;
  161. (_) ->
  162. %% (not found), or waiters registered
  163. false
  164. end, Existing) of
  165. true ->
  166. %% conflict; return 'false', indicating failure
  167. false;
  168. false ->
  169. %% possibly waiters, but they are handled in next step
  170. insert_objects(Existing),
  171. _ = gproc_lib:ensure_monitor(Pid, Scope),
  172. {true, Objs}
  173. end
  174. end.
  175. -spec insert_objects([{gproc:key(), pid(), any()}]) -> ok.
  176. insert_objects(Objs) ->
  177. lists:foreach(
  178. fun({{{Id,_} = _K, Pid, V} = Obj, Existing}) ->
  179. ets:insert(?TAB, [Obj, {{Pid, Id}, []}]),
  180. case Existing of
  181. [] -> ok;
  182. [{_, Waiters}] ->
  183. notify_waiters(Waiters, Id, Pid, V, registered)
  184. end
  185. end, Objs).
  186. await({T,C,_} = Key, WPid, {_Pid, Ref} = From) ->
  187. Rev = {{WPid,Key}, []},
  188. case ets:lookup(?TAB, {Key,T}) of
  189. [{_, P, Value}] ->
  190. %% for symmetry, we always reply with Ref and then send a message
  191. if C == g ->
  192. %% in the global case, we bundle the reply, since otherwise
  193. %% the messages can pass each other
  194. {reply, {Ref, {Key, P, Value}}};
  195. true ->
  196. gen_server:reply(From, Ref),
  197. WPid ! {gproc, Ref, registered, {Key, P, Value}},
  198. noreply
  199. end;
  200. [{K, Waiters}] ->
  201. NewWaiters = [{WPid,Ref} | Waiters],
  202. W = {K, NewWaiters},
  203. ets:insert(?TAB, [W, Rev]),
  204. _ = gproc_lib:ensure_monitor(WPid,C),
  205. {reply, Ref, [W,Rev]};
  206. [] ->
  207. W = {{Key,T}, [{WPid,Ref}]},
  208. ets:insert(?TAB, [W, Rev]),
  209. _ = gproc_lib:ensure_monitor(WPid,C),
  210. {reply, Ref, [W,Rev]}
  211. end.
  212. maybe_waiters(_, _, _, _, []) ->
  213. false;
  214. maybe_waiters(K, Pid, Value, T, Event) ->
  215. case ets:lookup(?TAB, {K,T}) of
  216. [{_, Waiters}] when is_list(Waiters) ->
  217. Followers = [F || {_,_,follow} = F <- Waiters],
  218. ets:insert(?TAB, [{{K,T}, Pid, Value},
  219. {{Pid,K}, [{monitor, Followers}
  220. || Followers =/= []]}]),
  221. notify_waiters(Waiters, K, Pid, Value, Event),
  222. true;
  223. _ ->
  224. false
  225. end.
  226. -spec notify_waiters([{pid(), reference()}], gproc:key(), pid(), any(), any()) -> ok.
  227. notify_waiters([{P, Ref}|T], K, Pid, V, E) ->
  228. P ! {gproc, Ref, registered, {K, Pid, V}},
  229. notify_waiters(T, K, Pid, V, E);
  230. notify_waiters([{P, Ref, follow}|T], K, Pid, V, E) ->
  231. %% This is really a monitor, lurking in the Waiters list
  232. P ! {gproc, E, Ref, K},
  233. notify_waiters(T, K, Pid, V, E);
  234. notify_waiters([], _, _, _, _) ->
  235. ok.
  236. remove_wait({T,_,_} = Key, Pid, Ref, Waiters) ->
  237. Rev = {Pid,Key},
  238. case remove_from_waiters(Waiters, Pid, Ref) of
  239. [] ->
  240. ets:delete(?TAB, {Key,T}),
  241. ets:delete(?TAB, Rev),
  242. [{delete, [{Key,T}, Rev], []}];
  243. NewWaiters ->
  244. ets:insert(?TAB, {Key, NewWaiters}),
  245. case lists:keymember(Pid, 1, NewWaiters) of
  246. true ->
  247. %% should be extremely unlikely
  248. [{insert, [{Key, NewWaiters}]}];
  249. false ->
  250. %% delete the reverse entry
  251. ets:delete(?TAB, Rev),
  252. [{insert, [{Key, NewWaiters}]},
  253. {delete, [Rev], []}]
  254. end
  255. end.
  256. remove_from_waiters(Waiters, Pid, all) ->
  257. [W || W <- Waiters,
  258. element(1,W) =/= Pid];
  259. remove_from_waiters(Waiters, Pid, Ref) ->
  260. [W || W <- Waiters, not is_waiter(W, Pid, Ref)].
  261. is_waiter({Pid, Ref} , Pid, Ref) -> true;
  262. is_waiter({Pid, Ref, _}, Pid, Ref) -> true;
  263. is_waiter(_, _, _) ->
  264. false.
  265. remove_monitors(Key, Pid, MPid) ->
  266. case ets:lookup(?TAB, {Pid, Key}) of
  267. [{_, r}] ->
  268. [];
  269. [{K, Opts}] when is_list(Opts) ->
  270. case lists:keyfind(monitors, 1, Opts) of
  271. false ->
  272. [];
  273. {_, Ms} ->
  274. Ms1 = [{P,R} || {P,R} <- Ms,
  275. P =/= MPid],
  276. NewMs = lists:keyreplace(monitors, 1, Opts, {monitors,Ms1}),
  277. ets:insert(?TAB, {K, NewMs}),
  278. [{insert, [{{Pid,Key}, NewMs}]}]
  279. end;
  280. _ ->
  281. []
  282. end.
  283. mk_reg_objs(T, Scope, Pid, L) when T==n; T==a; T==rc ->
  284. lists:map(fun({K,V}) ->
  285. {{{T,Scope,K},T}, Pid, V};
  286. (_) ->
  287. erlang:error(badarg)
  288. end, L);
  289. mk_reg_objs(p = T, Scope, Pid, L) ->
  290. lists:map(fun({K,V}) ->
  291. {{{T,Scope,K},Pid}, Pid, V};
  292. (_) ->
  293. erlang:error(badarg)
  294. end, L).
  295. mk_reg_rev_objs(T, Scope, Pid, L) ->
  296. [{{Pid,{T,Scope,K}}, []} || {K,_} <- L].
  297. ensure_monitor(shared, _) ->
  298. ok;
  299. ensure_monitor(Pid, _) when Pid == self() ->
  300. %% monitoring is ensured through a 'monitor_me' message
  301. ok;
  302. ensure_monitor(Pid, Scope) when Scope==g; Scope==l ->
  303. case ets:insert_new(?TAB, {{Pid, Scope}}) of
  304. false -> ok;
  305. true -> erlang:monitor(process, Pid)
  306. end.
  307. remove_reg(Key, Pid, Event) ->
  308. Reg = remove_reg_1(Key, Pid),
  309. Rev = remove_reverse_mapping(Event, Pid, Key),
  310. [Reg, Rev].
  311. remove_reg(Key, Pid, Event, Opts) ->
  312. Reg = remove_reg_1(Key, Pid),
  313. Rev = remove_reverse_mapping(Event, Pid, Key, Opts),
  314. [Reg, Rev].
  315. remove_reverse_mapping(Event, Pid, Key) ->
  316. Opts = case ets:lookup(?TAB, {Pid, Key}) of
  317. [] -> [];
  318. [{_, r}] -> [];
  319. [{_, L}] when is_list(L) ->
  320. L
  321. end,
  322. remove_reverse_mapping(Event, Pid, Key, Opts).
  323. remove_reverse_mapping(Event, Pid, Key, Opts) when Event==unreg;
  324. element(1,Event)==migrated;
  325. element(1,Event)==failover ->
  326. Rev = {Pid, Key},
  327. _ = notify(Event, Key, Opts),
  328. ets:delete(?TAB, Rev),
  329. Rev.
  330. notify(Key, Opts) ->
  331. notify(unreg, Key, Opts).
  332. monitors(Opts) ->
  333. case lists:keyfind(monitor, 1, Opts) of
  334. false ->
  335. [];
  336. {_, Mons} ->
  337. Mons
  338. end.
  339. standbys(Opts) ->
  340. select_monitors(monitors(Opts), standby, []).
  341. followers(Opts) ->
  342. select_monitors(monitors(Opts), follow, []).
  343. select_monitors([{_,_,Type}=H|T], Type, Acc) ->
  344. select_monitors(T, Type, [H|Acc]);
  345. select_monitors([_|T], Type, Acc) ->
  346. select_monitors(T, Type, Acc);
  347. select_monitors([], _, Acc) ->
  348. Acc.
  349. remove_monitor_pid([{monitor, Mons}|T], Pid) ->
  350. [{monitors, [M || M <- Mons,
  351. element(1, M) =/= Pid]}|T];
  352. remove_monitor_pid([H|T], Pid) ->
  353. [H | remove_monitor_pid(T, Pid)];
  354. remove_monitor_pid([], _) ->
  355. [].
  356. notify([], _, _) ->
  357. ok;
  358. notify(Event, Key, Opts) ->
  359. notify_(monitors(Opts), Event, Key).
  360. %% Also handle old-style monitors
  361. notify_([{Pid,Ref}|T], Event, Key) ->
  362. Pid ! {gproc, Event, Ref, Key},
  363. notify_(T, Event, Key);
  364. notify_([{Pid,Ref,_}|T], Event, {_,l,_} = Key) ->
  365. Pid ! {gproc, Event, Ref, Key},
  366. notify_(T, Event, Key);
  367. notify_([{Pid,Ref,_}|T], Event, {_,g,_} = Key) when node(Pid) == node() ->
  368. Pid ! {gproc, Event, Ref, Key},
  369. notify_(T, Event, Key);
  370. notify_([_|T], Event, Key) ->
  371. notify_(T, Event, Key);
  372. notify_([], _, _) ->
  373. ok.
  374. add_monitor([{monitor, Mons}|T], Pid, Ref, Type) ->
  375. [{monitor, [{Pid,Ref,Type}|Mons]}|T];
  376. add_monitor([H|T], Pid, Ref, Type) ->
  377. [H|add_monitor(T, Pid, Ref, Type)];
  378. add_monitor([], Pid, Ref, Type) ->
  379. [{monitor, [{Pid, Ref, Type}]}].
  380. remove_monitor([{monitor, Mons}|T], Pid, Ref) ->
  381. [{monitor, [Mon || Mon <- Mons, not is_mon(Mon,Pid,Ref)]} | T];
  382. remove_monitor([H|T], Pid, Ref) ->
  383. [H|remove_monitor(T, Pid, Ref)];
  384. remove_monitor([], _Pid, _Ref) ->
  385. [].
  386. is_mon({Pid,Ref,_}, Pid, Ref) -> true;
  387. is_mon({Pid,Ref}, Pid, Ref) -> true;
  388. is_mon(_, _, _) ->
  389. false.
  390. remove_many(T, Scope, L, Pid) ->
  391. lists:flatmap(fun(K) ->
  392. Key = {T, Scope, K},
  393. remove_reg(Key, Pid, unreg, unreg_opts(Key, Pid))
  394. end, L).
  395. unreg_opts(Key, Pid) ->
  396. case ets:lookup(?TAB, {Pid, Key}) of
  397. [] ->
  398. [];
  399. [{_,r}] ->
  400. [];
  401. [{_,Opts}] ->
  402. Opts
  403. end.
  404. remove_reg_1({c,_,_} = Key, Pid) ->
  405. remove_counter_1(Key, ets:lookup_element(?TAB, Reg = {Key,Pid}, 3), Pid),
  406. Reg;
  407. remove_reg_1({r,_,_} = Key, Pid) ->
  408. remove_resource_1(Key, ets:lookup_element(?TAB, Reg = {Key,Pid}, 3), Pid),
  409. Reg;
  410. remove_reg_1({T,_,_} = Key, _Pid) when T==a; T==n; T==rc ->
  411. ets:delete(?TAB, Reg = {Key,T}),
  412. Reg;
  413. remove_reg_1({_,_,_} = Key, Pid) ->
  414. ets:delete(?TAB, Reg = {Key, Pid}),
  415. Reg.
  416. remove_counter_1({c,C,N} = Key, Val, Pid) ->
  417. Res = ets:delete(?TAB, {Key, Pid}),
  418. update_aggr_counter(C, N, -Val),
  419. Res.
  420. remove_resource_1({r,C,N} = Key, _, Pid) ->
  421. Res = ets:delete(?TAB, {Key, Pid}),
  422. update_resource_count(C, N, -1),
  423. Res.
  424. do_set_value({T,_,_} = Key, Value, Pid) ->
  425. K2 = if Pid == shared -> shared;
  426. T==n orelse T==a orelse T==rc -> T;
  427. true -> Pid
  428. end,
  429. try ets:lookup_element(?TAB, {Key,K2}, 2) of
  430. Pid ->
  431. ets:insert(?TAB, {{Key, K2}, Pid, Value});
  432. _ ->
  433. false
  434. catch
  435. error:_ -> false
  436. end.
  437. do_set_counter_value({_,C,N} = Key, Value, Pid) ->
  438. OldVal = ets:lookup_element(?TAB, {Key, Pid}, 3), % may fail with badarg
  439. Res = ets:insert(?TAB, {{Key, Pid}, Pid, Value}),
  440. update_aggr_counter(C, N, Value - OldVal),
  441. Res.
  442. update_counter({T,l,Ctr} = Key, Incr, Pid) when is_integer(Incr), T==c;
  443. is_integer(Incr), T==n ->
  444. Res = ets:update_counter(?TAB, {Key, Pid}, {3,Incr}),
  445. if T==c ->
  446. update_aggr_counter(l, Ctr, Incr);
  447. true ->
  448. ok
  449. end,
  450. Res;
  451. update_counter({T,l,Ctr} = Key, {Incr, Threshold, SetValue}, Pid)
  452. when is_integer(Incr), is_integer(Threshold), is_integer(SetValue), T==c;
  453. is_integer(Incr), is_integer(Threshold), is_integer(SetValue), T==n ->
  454. [Prev, New] = ets:update_counter(?TAB, {Key, Pid},
  455. [{3, 0}, {3, Incr, Threshold, SetValue}]),
  456. if T==c ->
  457. update_aggr_counter(l, Ctr, New - Prev);
  458. true ->
  459. ok
  460. end,
  461. New;
  462. update_counter({T,l,Ctr} = Key, Ops, Pid) when is_list(Ops), T==c;
  463. is_list(Ops), T==r;
  464. is_list(Ops), T==n ->
  465. case ets:update_counter(?TAB, {Key, Pid},
  466. [{3, 0} | expand_ops(Ops)]) of
  467. [_] ->
  468. [];
  469. [Prev | Rest] ->
  470. [New | _] = lists:reverse(Rest),
  471. if T==c ->
  472. update_aggr_counter(l, Ctr, New - Prev);
  473. true ->
  474. ok
  475. end,
  476. Rest
  477. end;
  478. update_counter(_, _, _) ->
  479. ?THROW_GPROC_ERROR(badarg).
  480. expand_ops([{Incr,Thr,SetV}|T])
  481. when is_integer(Incr), is_integer(Thr), is_integer(SetV) ->
  482. [{3, Incr, Thr, SetV}|expand_ops(T)];
  483. expand_ops([Incr|T]) when is_integer(Incr) ->
  484. [{3, Incr}|expand_ops(T)];
  485. expand_ops([]) ->
  486. [];
  487. expand_ops(_) ->
  488. ?THROW_GPROC_ERROR(badarg).
  489. update_aggr_counter(C, N, Val) ->
  490. ?MAY_FAIL(ets:update_counter(?TAB, {{a,C,N},a}, {3, Val})).
  491. decrement_resource_count(C, N) ->
  492. update_resource_count(C, N, -1).
  493. update_resource_count(C, N, Val) ->
  494. try ets:update_counter(?TAB, {{rc,C,N},rc}, {3, Val}) of
  495. 0 ->
  496. resource_count_zero(C, N);
  497. _ ->
  498. ok
  499. catch
  500. _:_ -> ok
  501. end.
  502. resource_count_zero(C, N) ->
  503. case ets:lookup(?TAB, {K = {rc,C,N},rc}) of
  504. [{_, Pid, _}] ->
  505. case get_attr(on_zero, Pid, K, undefined) of
  506. undefined -> ok;
  507. Actions ->
  508. perform_on_zero(Actions, C, N, Pid)
  509. end;
  510. _ -> ok
  511. end.
  512. perform_on_zero(Actions, C, N, Pid) ->
  513. lists:foreach(
  514. fun(A) ->
  515. try perform_on_zero_(A, C, N, Pid)
  516. catch error:_ -> ignore
  517. end
  518. end, Actions).
  519. perform_on_zero_({send, ToProc}, C, N, Pid) ->
  520. gproc:send(ToProc, {gproc, resource_on_zero, C, N, Pid}),
  521. ok;
  522. perform_on_zero_({bcast, ToProc}, C, N, Pid) ->
  523. gproc:bcast(ToProc, {gproc, resource_on_zero, C, N, Pid}),
  524. ok;
  525. perform_on_zero_(publish, C, N, Pid) ->
  526. gproc_ps:publish(C, gproc_resource_on_zero, {C, N, Pid}),
  527. ok;
  528. perform_on_zero_({unreg_shared, T,N}, C, _, _) ->
  529. K = {T, C, N},
  530. case ets:member(?TAB, {K, shared}) of
  531. true ->
  532. Objs = remove_reg(K, shared, unreg),
  533. _ = if C == g -> self() ! {gproc_unreg, Objs};
  534. true -> ok
  535. end,
  536. ok;
  537. false ->
  538. ok
  539. end;
  540. perform_on_zero_(_, _, _, _) ->
  541. ok.
  542. scan_existing_counters(Ctxt, Name) ->
  543. Head = {{{c,Ctxt,Name},'_'},'_','$1'},
  544. Cs = ets:select(?TAB, [{Head, [], ['$1']}]),
  545. lists:sum(Cs).
  546. scan_existing_resources(Ctxt, Name) ->
  547. Head = {{{r,Ctxt,Name},'_'},'_','_'},
  548. ets:select_count(?TAB, [{Head, [], [true]}]).
  549. valid_opts(Type, Default) ->
  550. Opts = get_app_env(Type, Default),
  551. check_opts(Type, Opts).
  552. check_opts(Type, Opts) when is_list(Opts) ->
  553. Check = check_option_f(Type),
  554. lists:map(fun(X) ->
  555. case Check(X) of
  556. true -> X;
  557. false ->
  558. erlang:error({illegal_option, X}, [Type, Opts])
  559. end
  560. end, Opts);
  561. check_opts(Type, Other) ->
  562. erlang:error(invalid_options, [Type, Other]).
  563. check_option_f(ets_options) -> fun check_ets_option/1;
  564. check_option_f(server_options) -> fun check_server_option/1.
  565. check_ets_option({read_concurrency , B}) -> is_boolean(B);
  566. check_ets_option({write_concurrency, B}) -> is_boolean(B);
  567. check_ets_option(_) -> false.
  568. check_server_option({priority, P}) ->
  569. %% Forbid setting priority to 'low' since that would
  570. %% surely cause problems. Unsure about 'max'...
  571. lists:member(P, [normal, high, max]);
  572. check_server_option(_) ->
  573. %% assume it's a valid spawn option
  574. true.
  575. get_app_env(Key, Default) ->
  576. case application:get_env(Key) of
  577. undefined -> Default;
  578. {ok, undefined} -> Default;
  579. {ok, Value} -> Value
  580. end.