gproc_dist.erl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  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@erlang-solutions.com>
  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_dist).
  23. -behaviour(gen_leader).
  24. -export([start_link/0, start_link/1,
  25. reg/1, reg/2, unreg/1,
  26. mreg/2,
  27. set_value/2,
  28. give_away/2,
  29. update_counter/2]).
  30. -export([leader_call/1, leader_cast/1]).
  31. %%% internal exports
  32. -export([init/1,
  33. handle_cast/3,
  34. handle_call/4,
  35. handle_info/2,
  36. handle_leader_call/4,
  37. handle_leader_cast/3,
  38. handle_DOWN/3,
  39. elected/2, % original version
  40. elected/3,
  41. surrendered/3,
  42. from_leader/3,
  43. code_change/4,
  44. terminate/2]).
  45. -include("gproc.hrl").
  46. -define(SERVER, ?MODULE).
  47. -record(state, {
  48. always_broadcast = false,
  49. is_leader}).
  50. start_link() ->
  51. start_link({[node()|nodes()], []}).
  52. start_link(all) ->
  53. start_link({[node()|nodes()], []});
  54. start_link(Nodes) when is_list(Nodes) ->
  55. start_link({Nodes, []});
  56. start_link({Nodes, Opts}) ->
  57. gen_leader:start_link(
  58. ?SERVER, Nodes, Opts, ?MODULE, [], []).
  59. %% ?SERVER, Nodes, [],?MODULE, [], [{debug,[trace]}]).
  60. %% {@see gproc:reg/1}
  61. %%
  62. reg(Key) ->
  63. reg(Key, gproc:default(Key)).
  64. %%% @spec({Class,Scope, Key}, Value) -> true
  65. %%% @doc
  66. %%% Class = n - unique name
  67. %%% | p - non-unique property
  68. %%% | c - counter
  69. %%% | a - aggregated counter
  70. %%% Scope = l | g (global or local)
  71. %%%
  72. reg({_,g,_} = Key, Value) ->
  73. %% anything global
  74. leader_call({reg, Key, Value, self()});
  75. reg(_, _) ->
  76. erlang:error(badarg).
  77. mreg(T, KVL) ->
  78. if is_list(KVL) -> leader_call({mreg, T, g, KVL, self()});
  79. true -> erlang:error(badarg)
  80. end.
  81. unreg({_,g,_} = Key) ->
  82. leader_call({unreg, Key, self()});
  83. unreg(_) ->
  84. erlang:error(badarg).
  85. set_value({T,g,_} = Key, Value) when T==a; T==c ->
  86. if is_integer(Value) ->
  87. leader_call({set, Key, Value});
  88. true ->
  89. erlang:error(badarg)
  90. end;
  91. set_value({_,g,_} = Key, Value) ->
  92. leader_call({set, Key, Value, self()});
  93. set_value(_, _) ->
  94. erlang:error(badarg).
  95. give_away({_,g,_} = Key, To) ->
  96. leader_call({give_away, Key, To, self()}).
  97. update_counter({c,g,_} = Key, Incr) when is_integer(Incr) ->
  98. leader_call({update_counter, Key, Incr, self()});
  99. update_counter(_, _) ->
  100. erlang:error(badarg).
  101. %%% ==========================================================
  102. handle_cast(_Msg, S, _) ->
  103. {stop, unknown_cast, S}.
  104. handle_call(_, _, S, _) ->
  105. {reply, badarg, S}.
  106. handle_info({'DOWN', _MRef, process, Pid, _}, S) ->
  107. leader_cast({pid_is_DOWN, Pid}),
  108. %% ets:select_delete(?TAB, [{{{Pid,'_'}}, [], [true]}]),
  109. %% ets:delete(?TAB, Pid),
  110. %% lists:foreach(fun(Key) -> gproc_lib:remove_reg_1(Key, Pid) end, Keys),
  111. {ok, S};
  112. handle_info(_, S) ->
  113. {ok, S}.
  114. elected(S, _E) ->
  115. {ok, {globals,globs()}, S#state{is_leader = true}}.
  116. elected(S, _E, undefined) ->
  117. %% I have become leader; full synch
  118. {ok, {globals, globs()}, S#state{is_leader = true}};
  119. elected(S, _E, _Node) ->
  120. Synch = {globals, globs()},
  121. if not S#state.always_broadcast ->
  122. %% Another node recognized us as the leader.
  123. %% Don't broadcast all data to everyone else
  124. {reply, Synch, S};
  125. true ->
  126. %% Main reason for doing this is if we are using a gen_leader
  127. %% that doesn't support the 'reply' return value
  128. {ok, Synch, S}
  129. end.
  130. globs() ->
  131. ets:select(?TAB, [{{{{'_',g,'_'},'_'},'_','_'},[],['$_']}]).
  132. surrendered(S, {globals, Globs}, _E) ->
  133. %% globals from this node should be more correct in our table than
  134. %% in the leader's
  135. surrendered_1(Globs),
  136. {ok, S#state{is_leader = false}}.
  137. handle_DOWN(Node, S, _E) ->
  138. Head = {{{'_',g,'_'},'_'},'$1','_'},
  139. Gs = [{'==', {node,'$1'},Node}],
  140. Globs = ets:select(?TAB, [{Head, Gs, [{{{element,1,{element,1,'$_'}},
  141. {element,2,'$_'}}}]}]),
  142. case process_globals(Globs) of
  143. [] ->
  144. {ok, S};
  145. Broadcast ->
  146. {ok, Broadcast, S}
  147. end.
  148. %% ets:select_delete(?TAB, [{Head, Gs, [true]}]),
  149. %% {ok, [{delete, Globs}], S}.
  150. handle_leader_call({reg, {C,g,Name} = K, Value, Pid}, _From, S, _E) ->
  151. case gproc_lib:insert_reg(K, Value, Pid, g) of
  152. false ->
  153. {reply, badarg, S};
  154. true ->
  155. gproc_lib:ensure_monitor(Pid,g),
  156. Vals =
  157. if C == a ->
  158. ets:lookup(?TAB, {K,a});
  159. C == c ->
  160. [{{K,Pid},Pid,Value} | ets:lookup(?TAB,{{a,g,Name},a})];
  161. C == n ->
  162. [{{K,n},Pid,Value}];
  163. true ->
  164. [{{K,Pid},Pid,Value}]
  165. end,
  166. {reply, true, [{insert, Vals}], S}
  167. end;
  168. handle_leader_call({update_counter, {c,g,_Ctr} = Key, Incr, Pid}, _From, S, _E)
  169. when is_integer(Incr) ->
  170. try New = ets:update_counter(?TAB, {Key, Pid}, {3,Incr}),
  171. Vals = [{{Key,Pid},Pid,New} | update_aggr_counter(Key, Incr)],
  172. {reply, New, [{insert, Vals}], S}
  173. catch
  174. error:_ ->
  175. {reply, badarg, S}
  176. end;
  177. handle_leader_call({unreg, {T,g,Name} = K, Pid}, _From, S, _E) ->
  178. Key = if T == n; T == a -> {K,T};
  179. true -> {K, Pid}
  180. end,
  181. case ets:member(?TAB, Key) of
  182. true ->
  183. gproc_lib:remove_reg(K, Pid),
  184. if T == c ->
  185. case ets:lookup(?TAB, {{a,g,Name},a}) of
  186. [Aggr] ->
  187. %% updated by remove_reg/2
  188. {reply, true, [{delete,[Key, {Pid,K}]},
  189. {insert, [Aggr]}], S};
  190. [] ->
  191. {reply, true, [{delete, [Key, {Pid,K}]}], S}
  192. end;
  193. true ->
  194. {reply, true, [{delete, [Key]}], S}
  195. end;
  196. false ->
  197. {reply, badarg, S}
  198. end;
  199. handle_leader_call({give_away, {T,g,_} = K, To, Pid}, _From, S, _E)
  200. when T == a; T == n ->
  201. Key = {K, T},
  202. case ets:lookup(?TAB, Key) of
  203. [{_, Pid, Value}] ->
  204. case pid_to_give_away_to(To) of
  205. Pid ->
  206. {reply, Pid, S};
  207. ToPid when is_pid(ToPid) ->
  208. ets:insert(?TAB, [{Key, ToPid, Value},
  209. {{ToPid,K}, r}]),
  210. gproc_lib:ensure_monitor(ToPid, g),
  211. {reply, ToPid, [{delete, [Key, {Pid,K}]},
  212. {insert, [{Key, ToPid, Value}]}], S};
  213. undefined ->
  214. ets:delete(?TAB, Key),
  215. ets:delete(?TAB, {Pid, K}),
  216. {reply, undefined, [{delete, [Key, {Pid,K}]}], S}
  217. end;
  218. _ ->
  219. {reply, badarg, S}
  220. end;
  221. handle_leader_call({mreg, T, g, L, Pid}, _From, S, _E) ->
  222. if T==p; T==n ->
  223. try gproc_lib:insert_many(T, g, L, Pid) of
  224. {true,Objs} -> {reply, true, [{insert,Objs}], S};
  225. false -> {reply, badarg, S}
  226. catch
  227. error:_ -> {reply, badarg, S}
  228. end;
  229. true -> {reply, badarg, S}
  230. end;
  231. handle_leader_call({set,{T,g,N} =K,V,Pid}, _From, S, _E) ->
  232. if T == a ->
  233. if is_integer(V) ->
  234. case gproc_lib:do_set_value(K, V, Pid) of
  235. true -> {reply, true, [{insert,[{{K,T},Pid,V}]}], S};
  236. false -> {reply, badarg, S}
  237. end
  238. end;
  239. T == c ->
  240. try gproc_lib:do_set_counter_value(K, V, Pid),
  241. AKey = {{a,g,N},a},
  242. Aggr = ets:lookup(?TAB, AKey), % may be []
  243. {reply, true, [{insert, [{{K,Pid},Pid,V} | Aggr]}], S}
  244. catch
  245. error:_ ->
  246. {reply, badarg, S}
  247. end;
  248. true ->
  249. case gproc_lib:do_set_value(K, V, Pid) of
  250. true ->
  251. Obj = if T==n -> {{K, T}, Pid, V};
  252. true -> {{K, Pid}, Pid, V}
  253. end,
  254. {reply, true, [{insert,[Obj]}], S};
  255. false ->
  256. {reply, badarg, S}
  257. end
  258. end;
  259. handle_leader_call({await, Key, Pid}, {_,Ref} = From, S, _E) ->
  260. %% The pid in _From is of the gen_leader instance that forwarded the
  261. %% call - not of the client. This is why the Pid is explicitly passed.
  262. %% case gproc_lib:await(Key, {Pid,Ref}) of
  263. case gproc_lib:await(Key, Pid, From) of
  264. {reply, {Ref, {K, P, V}}} ->
  265. {reply, {Ref, {K, P, V}}, S};
  266. {reply, Reply, Insert} ->
  267. {reply, Reply, [{insert, Insert}], S}
  268. end;
  269. handle_leader_call(_, _, S, _E) ->
  270. {reply, badarg, S}.
  271. handle_leader_cast({add_globals, Missing}, S, _E) ->
  272. %% This is an audit message: a peer (non-leader) had info about granted
  273. %% global resources that we didn't know of when we became leader.
  274. %% This could happen due to a race condition when the old leader died.
  275. ets:insert(?TAB, Missing),
  276. {ok, [{insert, Missing}], S};
  277. handle_leader_cast({remove_globals, Globals}, S, _E) ->
  278. delete_globals(Globals),
  279. {ok, S};
  280. handle_leader_cast({pid_is_DOWN, Pid}, S, _E) ->
  281. Globals = ets:select(?TAB, [{{{Pid,'$1'},r},
  282. [{'==',{element,2,'$1'},g}],[{{'$1',Pid}}]}]),
  283. ets:delete(?TAB, {Pid,g}),
  284. case process_globals(Globals) of
  285. [] ->
  286. {ok, S};
  287. Broadcast ->
  288. {ok, Broadcast, S}
  289. end.
  290. process_globals(Globals) ->
  291. Modified =
  292. lists:foldl(
  293. fun({{T,_,_} = Key, Pid}, A) ->
  294. A1 = case T of
  295. c ->
  296. Incr = ets:lookup_element(?TAB, {Key,Pid}, 3),
  297. update_aggr_counter(Key, -Incr) ++ A;
  298. _ ->
  299. A
  300. end,
  301. K = ets_key(Key, Pid),
  302. ets:delete(?TAB, K),
  303. ets:delete(?TAB, {Pid,Key}),
  304. A1
  305. end, [], Globals),
  306. [{Op,Objs} || {Op,Objs} <- [{insert,Modified},
  307. {delete,Globals}], Objs =/= []].
  308. code_change(_FromVsn, S, _Extra, _E) ->
  309. {ok, S}.
  310. terminate(_Reason, _S) ->
  311. ok.
  312. from_leader(Ops, S, _E) ->
  313. lists:foreach(
  314. fun({delete, Globals}) ->
  315. delete_globals(Globals);
  316. ({insert, Globals}) ->
  317. ets:insert(?TAB, Globals),
  318. lists:foreach(
  319. fun({{{_,g,_}=Key,_}, P, _}) ->
  320. ets:insert(?TAB, {{P,Key},r}),
  321. gproc_lib:ensure_monitor(P,g);
  322. ({{P,_K},r}) ->
  323. gproc_lib:ensure_monitor(P,g);
  324. (_) ->
  325. skip
  326. end, Globals)
  327. end, Ops),
  328. {ok, S}.
  329. delete_globals(Globals) ->
  330. lists:foreach(
  331. fun({{_,g,_},T} = K) when is_atom(T) ->
  332. ets:delete(?TAB, K);
  333. ({Key, Pid}) when is_pid(Pid) ->
  334. K = ets_key(Key,Pid),
  335. ets:delete(?TAB, K),
  336. ets:delete(?TAB, {Pid, Key});
  337. ({Pid, K}) when is_pid(Pid) ->
  338. ets:delete(?TAB, {Pid, K})
  339. %% case node(Pid) =:= node() of
  340. %% true ->
  341. %% ets:delete(?TAB, {Pid,g});
  342. %% _ -> ok
  343. %% end
  344. end, Globals).
  345. ets_key({T,_,_} = K, _) when T==n; T==a ->
  346. {K, T};
  347. ets_key(K, Pid) ->
  348. {K, Pid}.
  349. leader_call(Req) ->
  350. case gen_leader:leader_call(?MODULE, Req) of
  351. badarg -> erlang:error(badarg, Req);
  352. Reply -> Reply
  353. end.
  354. leader_cast(Msg) ->
  355. gen_leader:leader_cast(?MODULE, Msg).
  356. init(Opts) ->
  357. S0 = #state{},
  358. AlwaysBcast = proplists:get_value(always_broadcast, Opts,
  359. S0#state.always_broadcast),
  360. {ok, #state{always_broadcast = AlwaysBcast}}.
  361. surrendered_1(Globs) ->
  362. My_local_globs =
  363. ets:select(?TAB, [{{{{'_',g,'_'},'_'},'$1', '_'},
  364. [{'==', {node,'$1'}, node()}],
  365. ['$_']}]),
  366. %% remove all remote globals - we don't have monitors on them.
  367. ets:select_delete(?TAB, [{{{{'_',g,'_'},'_'}, '$1', '_'},
  368. [{'=/=', {node,'$1'}, node()}],
  369. [true]}]),
  370. %% insert new non-local globals, collect the leader's version of
  371. %% what my globals are
  372. Ldr_local_globs =
  373. lists:foldl(
  374. fun({{Key,_}=K, Pid, V}, Acc) when node(Pid) =/= node() ->
  375. ets:insert(?TAB, [{K, Pid, V}, {{Pid,Key}}]),
  376. Acc;
  377. ({_, Pid, _} = Obj, Acc) when node(Pid) == node() ->
  378. [Obj|Acc]
  379. end, [], Globs),
  380. case [{K,P,V} || {K,P,V} <- My_local_globs,
  381. not(lists:keymember(K, 1, Ldr_local_globs))] of
  382. [] ->
  383. %% phew! We have the same picture
  384. ok;
  385. [_|_] = Missing ->
  386. %% This is very unlikely, I think
  387. leader_cast({add_globals, Missing})
  388. end,
  389. case [{K,P} || {K,P,_} <- Ldr_local_globs,
  390. not(lists:keymember(K, 1, My_local_globs))] of
  391. [] ->
  392. ok;
  393. [_|_] = Remove ->
  394. leader_cast({remove_globals, Remove})
  395. end.
  396. update_aggr_counter({c,g,Ctr}, Incr) ->
  397. Key = {{a,g,Ctr},a},
  398. case ets:lookup(?TAB, Key) of
  399. [] ->
  400. [];
  401. [{K, Pid, Prev}] ->
  402. New = {K, Pid, Prev+Incr},
  403. ets:insert(?TAB, New),
  404. [New]
  405. end.
  406. pid_to_give_away_to(P) when is_pid(P) ->
  407. P;
  408. pid_to_give_away_to({T,g,_} = Key) when T==n; T==a ->
  409. case ets:lookup(?TAB, {Key, T}) of
  410. [{_, Pid, _}] ->
  411. Pid;
  412. _ ->
  413. undefined
  414. end.
  415. %% -ifdef(TEST).
  416. %% dist_test_() ->
  417. %% {timeout, 60,
  418. %% [{foreach,
  419. %% fun() ->
  420. %% Ns = start_slaves([n1, n2]),
  421. %% %% dbg:tracer(),
  422. %% %% [dbg:n(N) || N <- Ns],
  423. %% %% dbg:tpl(gproc_dist, x),
  424. %% %% dbg:p(all,[c]),
  425. %% Ns
  426. %% end,
  427. %% fun(Ns) ->
  428. %% [rpc:call(N, init, stop, []) || N <- Ns]
  429. %% end,
  430. %% [
  431. %% {with, [fun(Ns) -> {in_parallel, [fun(X) -> t_simple_reg(X) end,
  432. %% fun(X) -> t_await_reg(X) end,
  433. %% fun(X) -> t_give_away(X) end]
  434. %% }
  435. %% end]}
  436. %% ]}
  437. %% ]}.
  438. %% -define(T_NAME, {n, g, {?MODULE, ?LINE}}).
  439. %% t_simple_reg([H|_] = Ns) ->
  440. %% ?debugMsg(t_simple_reg),
  441. %% Name = ?T_NAME,
  442. %% P = t_spawn_reg(H, Name),
  443. %% ?assertMatch(ok, t_lookup_everywhere(Name, Ns, P)),
  444. %% ?assertMatch(true, t_call(P, {apply, gproc, unreg, [Name]})),
  445. %% ?assertMatch(ok, t_lookup_everywhere(Name, Ns, undefined)),
  446. %% ?assertMatch(ok, t_call(P, die)).
  447. %% t_await_reg([A,B|_]) ->
  448. %% ?debugMsg(t_await_reg),
  449. %% Name = ?T_NAME,
  450. %% P = t_spawn(A),
  451. %% P ! {self(), {apply, gproc, await, [Name]}},
  452. %% P1 = t_spawn_reg(B, Name),
  453. %% ?assert(P1 == receive
  454. %% {P, Res} ->
  455. %% element(1, Res)
  456. %% end),
  457. %% ?assertMatch(ok, t_call(P, die)),
  458. %% ?assertMatch(ok, t_call(P1, die)).
  459. %% t_give_away([A,B|_] = Ns) ->
  460. %% ?debugMsg(t_give_away),
  461. %% Na = ?T_NAME,
  462. %% Nb = ?T_NAME,
  463. %% Pa = t_spawn_reg(A, Na),
  464. %% Pb = t_spawn_reg(B, Nb),
  465. %% ?assertMatch(ok, t_lookup_everywhere(Na, Ns, Pa)),
  466. %% ?assertMatch(ok, t_lookup_everywhere(Nb, Ns, Pb)),
  467. %% %% ?debugHere,
  468. %% ?assertMatch(Pb, t_call(Pa, {apply, {gproc, give_away, [Na, Nb]}})),
  469. %% ?assertMatch(ok, t_lookup_everywhere(Na, Ns, Pb)),
  470. %% %% ?debugHere,
  471. %% ?assertMatch(Pa, t_call(Pa, {apply, {gproc, give_away, [Na, Pa]}})),
  472. %% ?assertMatch(ok, t_lookup_everywhere(Na, Ns, Pa)),
  473. %% %% ?debugHere,
  474. %% ?assertMatch(ok, t_call(Pa, die)),
  475. %% ?assertMatch(ok, t_call(Pb, die)).
  476. %% t_sleep() ->
  477. %% timer:sleep(1000).
  478. %% t_lookup_everywhere(Key, Nodes, Exp) ->
  479. %% t_lookup_everywhere(Key, Nodes, Exp, 3).
  480. %% t_lookup_everywhere(Key, _, Exp, 0) ->
  481. %% {lookup_failed, Key, Exp};
  482. %% t_lookup_everywhere(Key, Nodes, Exp, I) ->
  483. %% Expected = [{N, Exp} || N <- Nodes],
  484. %% Found = [{N,rpc:call(N, gproc, where, [Key])} || N <- Nodes],
  485. %% if Expected =/= Found ->
  486. %% ?debugFmt("lookup ~p failed (~p), retrying...~n", [Key, Found]),
  487. %% t_sleep(),
  488. %% t_lookup_everywhere(Key, Nodes, Exp, I-1);
  489. %% true ->
  490. %% ok
  491. %% end.
  492. %% t_spawn(Node) ->
  493. %% Me = self(),
  494. %% P = spawn(Node, fun() ->
  495. %% Me ! {self(), ok},
  496. %% t_loop()
  497. %% end),
  498. %% receive
  499. %% {P, ok} -> P
  500. %% end.
  501. %% t_spawn_reg(Node, Name) ->
  502. %% Me = self(),
  503. %% spawn(Node, fun() ->
  504. %% ?assertMatch(true, gproc:reg(Name)),
  505. %% Me ! {self(), ok},
  506. %% t_loop()
  507. %% end),
  508. %% receive
  509. %% {P, ok} -> P
  510. %% end.
  511. %% t_call(P, Req) ->
  512. %% P ! {self(), Req},
  513. %% receive
  514. %% {P, Res} ->
  515. %% Res
  516. %% end.
  517. %% t_loop() ->
  518. %% receive
  519. %% {From, die} ->
  520. %% From ! {self(), ok};
  521. %% {From, {apply, M, F, A}} ->
  522. %% From ! {self(), apply(M, F, A)},
  523. %% t_loop()
  524. %% end.
  525. %% start_slaves(Ns) ->
  526. %% [H|T] = Nodes = [start_slave(N) || N <- Ns],
  527. %% %% ?debugVal([pong = rpc:call(H, net, ping, [N]) || N <- T]),
  528. %% %% ?debugVal(rpc:multicall(Nodes, application, start, [gproc])),
  529. %% Nodes.
  530. %% start_slave(Name) ->
  531. %% case node() of
  532. %% nonode@nohost ->
  533. %% os:cmd("epmd -daemon"),
  534. %% {ok, _} = net_kernel:start([gproc_master, shortnames]);
  535. %% _ ->
  536. %% ok
  537. %% end,
  538. %% {ok, Node} = slave:start(
  539. %% host(), Name,
  540. %% "-pa . -pz ../ebin -pa ../deps/gen_leader/ebin "
  541. %% "-gproc gproc_dist all"),
  542. %% %% io:fwrite(user, "Slave node: ~p~n", [Node]),
  543. %% Node.
  544. %% host() ->
  545. %% [Name, Host] = re:split(atom_to_list(node()), "@", [{return, list}]),
  546. %% list_to_atom(Host).
  547. %% -endif.