syn.erl 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. %% ==========================================================================================================
  2. %% Syn - A global Process Registry and Process Group manager.
  3. %%
  4. %% The MIT License (MIT)
  5. %%
  6. %% Copyright (c) 2015-2021 Roberto Ostinelli <roberto@ostinelli.net> and Neato Robotics, Inc.
  7. %%
  8. %% Permission is hereby granted, free of charge, to any person obtaining a copy
  9. %% of this software and associated documentation files (the "Software"), to deal
  10. %% in the Software without restriction, including without limitation the rights
  11. %% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. %% copies of the Software, and to permit persons to whom the Software is
  13. %% furnished to do so, subject to the following conditions:
  14. %%
  15. %% The above copyright notice and this permission notice shall be included in
  16. %% all copies or substantial portions of the Software.
  17. %%
  18. %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. %% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. %% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. %% THE SOFTWARE.
  25. %% ==========================================================================================================
  26. %% ===================================================================
  27. %% @doc Exposes all of the global Process Registry and Process Group APIs.
  28. %%
  29. %% Syn implement Scopes. You may think of Scopes such as database tables, so a set of data elements,
  30. %% but that's where the analogy ends.
  31. %%
  32. %% A Scope is a way to create a namespaced, logical overlay network running on top of the Erlang distribution cluster.
  33. %% Nodes that belong to the same Scope will form a subcluster: they will synchronize data between themselves,
  34. %% and themselves only.
  35. %%
  36. %% For instance, you may have nodes in your Erlang cluster that need to handle connections to users, and other nodes
  37. %% that need to handle connections to physical devices. One approach is to create two Scopes: `users' and `devices',
  38. %% where you can register your different types of connections.
  39. %%
  40. %% Scopes are therefore a way to properly namespace your logic, but they also allow to build considerably larger
  41. %% scalable architectures, as it is possible to divide an Erlang cluster into subclusters which hold specific portions
  42. %% of data.
  43. %%
  44. %% Please note any of the methods documented here will raise:
  45. %% <ul>
  46. %% <li>An `error({invalid_scope, Scope})' if the local node has not been added to the specified Scope.</li>
  47. %% <li>An `error({invalid_remote_scope, Scope, RemoteNode})' if the Pid passed in as variable is running on a
  48. %% node that has not been added to the specified Scope, or if the remote scope process is temporarily down.</li>
  49. %% </ul>
  50. %%
  51. %% <h2>Quickstart</h2>
  52. %% <h3>Registry</h3>
  53. %% <h4>Elixir</h4>
  54. %% ```
  55. %% iex> :syn.add_node_to_scopes([:users])
  56. %% :ok
  57. %% iex> pid = self()
  58. %% #PID<0.105.0>
  59. %% iex> :syn.register(:users, "hedy", pid)
  60. %% :ok
  61. %% iex> :syn.lookup(:users, "hedy")
  62. %% {#PID<0.105.0>,:undefined}
  63. %% iex> :syn.register(:users, "hedy", pid, [city: "Milan"])
  64. %% :ok
  65. %% iex> :syn.lookup(:users, "hedy")
  66. %% {#PID<0.105.0>,[city: "Milan"]}
  67. %% iex> :syn.registry_count(:users)
  68. %% 1
  69. %% '''
  70. %% <h4>Erlang</h4>
  71. %% ```
  72. %% 1> syn:add_node_to_scopes([users]).
  73. %% ok
  74. %% 2> Pid = self().
  75. %% <0.93.0>
  76. %% 3> syn:register(users, "hedy", Pid).
  77. %% ok
  78. %% 4> syn:lookup(users, "hedy").
  79. %% {<0.93.0>,undefined}
  80. %% 5> syn:register(users, "hedy", Pid, [{city, "Milan"}]).
  81. %% ok
  82. %% 6> syn:lookup(users, "hedy").
  83. %% {<0.93.0>,[{city, "Milan"}]}
  84. %% 7> syn:registry_count(users).
  85. %% 1
  86. %% '''
  87. %% <h3>Process Groups</h3>
  88. %% <h4>Elixir</h4>
  89. %% ```
  90. %% iex> :syn.add_node_to_scopes([:users])
  91. %% :ok
  92. %% iex> pid = self()
  93. %% #PID<0.88.0>
  94. %% iex> :syn.join(:users, {:italy, :lombardy}, pid)
  95. %% :ok
  96. %% iex> :syn.members(:users, {:italy, :lombardy})
  97. %% [#PID<0.88.0>,:undefined}]
  98. %% iex> :syn.is_member(:users, {:italy, :lombardy}, pid)
  99. %% true
  100. %% iex> :syn.publish(:users, {:italy, :lombardy}, "hello lombardy!")
  101. %% {:ok,1}
  102. %% iex> flush()
  103. %% Shell got "hello lombardy!"
  104. %% ok
  105. %% '''
  106. %% <h4>Erlang</h4>
  107. %% ```
  108. %% 1> syn:add_node_to_scopes([users]).
  109. %% ok
  110. %% 2> Pid = self().
  111. %% <0.88.0>
  112. %% 3> syn:join(users, {italy, lombardy}, Pid).
  113. %% ok
  114. %% 4> syn:members(users, {italy, lombardy}).
  115. %% [{<0.88.0>,undefined}]
  116. %% 5> syn:is_member(users, {italy, lombardy}, Pid).
  117. %% true
  118. %% 6> syn:publish(users, {italy, lombardy}, "hello lombardy!").
  119. %% {ok,1}
  120. %% 7> flush().
  121. %% Shell got "hello lombardy!"
  122. %% ok
  123. %% '''
  124. %% @end
  125. %% ===================================================================
  126. -module(syn).
  127. %% API
  128. -export([start/0, stop/0]).
  129. %% scopes
  130. -export([node_scopes/0, add_node_to_scopes/1]).
  131. -export([subcluster_nodes/2]).
  132. -export([set_event_handler/1]).
  133. %% registry
  134. -export([lookup/2]).
  135. -export([register/3, register/4]).
  136. -export([unregister/2]).
  137. -export([registry_count/1, registry_count/2]).
  138. -export([local_registry_count/1]).
  139. %% gen_server via interface
  140. -export([register_name/2, unregister_name/1, whereis_name/1, send/2]).
  141. %% groups
  142. -export([members/2, is_member/3]).
  143. -export([local_members/2, is_local_member/3]).
  144. -export([join/3, join/4]).
  145. -export([leave/3]).
  146. -export([group_count/1, group_count/2]).
  147. -export([local_group_count/1]).
  148. -export([group_names/1, group_names/2]).
  149. -export([local_group_names/1]).
  150. -export([publish/3]).
  151. -export([local_publish/3]).
  152. -export([multi_call/3, multi_call/4, multi_call_reply/2]).
  153. %% macros
  154. -define(DEFAULT_MULTI_CALL_TIMEOUT_MS, 5000).
  155. %% API
  156. %% ===================================================================
  157. %% @doc Starts Syn manually.
  158. %%
  159. %% In most cases Syn will be started as one of your application's dependencies,
  160. %% however you may use this helper method to start it manually.
  161. -spec start() -> ok.
  162. start() ->
  163. {ok, _} = application:ensure_all_started(syn),
  164. ok.
  165. %% @doc Stops Syn manually.
  166. -spec stop() -> ok | {error, Reason :: term()}.
  167. stop() ->
  168. application:stop(syn).
  169. %% ----- \/ scopes ---------------------------------------------------
  170. %% @doc Retrieves the Scopes that the node has been added to.
  171. -spec node_scopes() -> [atom()].
  172. node_scopes() ->
  173. syn_sup:node_scopes().
  174. %% @doc Add the local node to the specified `Scopes'.
  175. %%
  176. %% There are 2 ways to add a node to Scopes. One is by using this method, the other is to set the environment variable `syn'
  177. %% with the key `scopes'. In this latter case, you're probably best off using an application configuration file:
  178. %%
  179. %% You only need to add a node to a scope once.
  180. %% <h3>Elixir</h3>
  181. %% ```
  182. %% config :syn,
  183. %% scopes: [:devices, :users]
  184. %% '''
  185. %% <h3>Erlang</h3>
  186. %% ```
  187. %% {syn, [
  188. %% {scopes, [:devices, :users]}
  189. %% ]}
  190. %% '''
  191. %%
  192. %% <h2>Examples</h2>
  193. %% <h3>Elixir</h3>
  194. %% ```
  195. %% iex> :syn.add_node_to_scopes([:devices])
  196. %% :ok
  197. %% '''
  198. %% <h3>Erlang</h3>
  199. %% ```
  200. %% 1> syn:add_node_to_scopes([devices]).
  201. %% ok
  202. %% '''
  203. -spec add_node_to_scopes(Scopes :: [atom()]) -> ok.
  204. add_node_to_scopes(Scopes) when is_list(Scopes) ->
  205. lists:foreach(fun(Scope) ->
  206. syn_sup:add_node_to_scope(Scope)
  207. end, Scopes).
  208. %% @doc Returns the nodes of the subcluster for the specified `Scope'.
  209. -spec subcluster_nodes(Manager :: registry | pg, Scope :: atom()) -> [node()].
  210. subcluster_nodes(registry, Scope) ->
  211. syn_registry:subcluster_nodes(Scope);
  212. subcluster_nodes(pg, Scope) ->
  213. syn_pg:subcluster_nodes(Scope).
  214. %% @doc Sets the handler module.
  215. %%
  216. %% Please see {@link syn_event_handler} for information on callbacks.
  217. %%
  218. %% There are 2 ways to set a handler module. One is by using this method, the other is to set the environment variable `syn'
  219. %% with the key `scopes'. In this latter case, you're probably best off using an application configuration file:
  220. %%
  221. %% <h3>Elixir</h3>
  222. %% ```
  223. %% config :syn,
  224. %% event_handler: MyCustomEventHandler
  225. %% '''
  226. %% <h3>Erlang</h3>
  227. %% ```
  228. %% {syn, [
  229. %% {event_handler, my_custom_event_handler}
  230. %% ]}
  231. %% '''
  232. %%
  233. %% <h2>Examples</h2>
  234. %% <h3>Elixir</h3>
  235. %% ```
  236. %% iex> :syn.set_event_handler(MyCustomEventHandler)
  237. %% ok
  238. %% '''
  239. %% <h3>Erlang</h3>
  240. %% ```
  241. %% 1> syn:set_event_handler(my_custom_event_handler).
  242. %% ok
  243. %% '''
  244. -spec set_event_handler(module()) -> ok.
  245. set_event_handler(Module) ->
  246. application:set_env(syn, event_handler, Module),
  247. %% ensure event handler is loaded
  248. syn_event_handler:ensure_event_handler_loaded().
  249. %% ----- \/ registry -------------------------------------------------
  250. %% @doc Looks up a registry entry in the specified `Scope'.
  251. %%
  252. %% <h2>Examples</h2>
  253. %% <h3>Elixir</h3>
  254. %% ```
  255. %% iex> :syn.register(:devices, "SN-123-456789", self())
  256. %% :ok
  257. %% iex> :syn.lookup(:devices, "SN-123-456789")
  258. %% {#PID<0.105.0>, undefined}
  259. %% '''
  260. %% <h3>Erlang</h3>
  261. %% ```
  262. %% 1> syn:register(devices, "SN-123-456789", self()).
  263. %% ok
  264. %% 2> syn:lookup(devices, "SN-123-456789").
  265. %% {<0.79.0>, undefined}
  266. %% '''
  267. -spec lookup(Scope :: atom(), Name :: term()) -> {pid(), Meta :: term()} | undefined.
  268. lookup(Scope, Name) ->
  269. syn_registry:lookup(Scope, Name).
  270. %% @equiv register(Scope, Name, Pid, undefined)
  271. %% @end
  272. -spec register(Scope :: atom(), Name :: term(), Pid :: term()) -> ok | {error, Reason :: term()}.
  273. register(Scope, Name, Pid) ->
  274. register(Scope, Name, Pid, undefined).
  275. %% @doc Registers a process with metadata in the specified `Scope'.
  276. %%
  277. %% Possible error reasons:
  278. %% <ul>
  279. %% <li>`taken': name is already registered with another `pid()'.</li>
  280. %% <li>`not_alive': The `pid()' being registered is not alive.</li>
  281. %% </ul>
  282. %%
  283. %% You may re-register a process multiple times, for example if you need to update its metadata.
  284. %% When a process gets registered, Syn will automatically monitor it. You may also register the same process with different names.
  285. %%
  286. %% <h2>Examples</h2>
  287. %% <h3>Elixir</h3>
  288. %% ```
  289. %% iex> :syn.register(:devices, "SN-123-456789", self(), [meta: :one])
  290. %% :ok
  291. %% iex> :syn.lookup(:devices, "SN-123-456789")
  292. %% {#PID<0.105.0>, [meta: :one]}
  293. %% '''
  294. %% <h3>Erlang</h3>
  295. %% ```
  296. %% 1> syn:register(devices, "SN-123-456789", self(), [{meta, one}]).
  297. %% ok
  298. %% 2> syn:lookup(devices, "SN-123-456789")
  299. %% {<0.105.0>,[{meta, one}]}
  300. %% '''
  301. %%
  302. %% Processes can also be registered as `gen_server' names, by usage of via-tuples. This way, you can use the `gen_server'
  303. %% API with these tuples without referring to the Pid directly. If you do so, you MUST use a `gen_server' name
  304. %% in format `{Scope, Name}', i.e. your via tuple will look like `{via, syn, {my_scope, <<"process name">>}}'.
  305. %% See here below for examples.
  306. %% <h2>Examples</h2>
  307. %% <h3>Elixir</h3>
  308. %% ```
  309. %% iex> tuple = {:via, :syn, {:devices, "SN-123-456789"}}.
  310. %% {:via, :syn, {:devices, "SN-123-456789"}}
  311. %% iex> GenServer.start_link(__MODULE__, [], name: tuple)
  312. %% {ok, #PID<0.105.0>}
  313. %% iex> GenServer.call(tuple, :your_message)
  314. %% :your_message
  315. %% '''
  316. %% <h3>Erlang</h3>
  317. %% ```
  318. %% 1> Tuple = {via, syn, {devices, "SN-123-456789"}}.
  319. %% {via, syn, {devices, "SN-123-456789"}}
  320. %% 2> gen_server:start_link(Tuple, your_module, []).
  321. %% {ok, <0.79.0>}
  322. %% 3> gen_server:call(Tuple, your_message).
  323. %% your_message
  324. %% '''
  325. -spec register(Scope :: atom(), Name :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
  326. register(Scope, Name, Pid, Meta) ->
  327. syn_registry:register(Scope, Name, Pid, Meta).
  328. %% @doc Unregisters a process from specified `Scope'.
  329. %%
  330. %% Possible error reasons:
  331. %% <ul>
  332. %% <li>`undefined': name is not registered.</li>
  333. %% <li>`race_condition': the local `pid()' does not correspond to the cluster value, so Syn will not succeed
  334. %% unregistering the value and will wait for the cluster to synchronize. This is a rare occasion.</li>
  335. %% </ul>
  336. %%
  337. %% You don't need to unregister names of processes that are about to die, since they are monitored by Syn
  338. %% and they will be removed automatically.
  339. -spec unregister(Scope :: atom(), Name :: term()) -> ok | {error, Reason :: term()}.
  340. unregister(Scope, Name) ->
  341. syn_registry:unregister(Scope, Name).
  342. %% @doc Returns the count of all registered processes for the specified `Scope'.
  343. %%
  344. %% <h2>Examples</h2>
  345. %% <h3>Elixir</h3>
  346. %% ```
  347. %% iex> :syn.registry_count(:devices)
  348. %% 512473
  349. %% '''
  350. %% <h3>Erlang</h3>
  351. %% ```
  352. %% 1> syn:registry_count(devices).
  353. %% 512473
  354. %% '''
  355. -spec registry_count(Scope :: atom()) -> non_neg_integer().
  356. registry_count(Scope) ->
  357. syn_registry:count(Scope).
  358. %% @doc Returns the count of all registered processes for the specified `Scope' running on a node.
  359. -spec registry_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
  360. registry_count(Scope, Node) ->
  361. syn_registry:count(Scope, Node).
  362. %% @equiv registry_count(Scope, node())
  363. %% @end
  364. -spec local_registry_count(Scope :: atom()) -> non_neg_integer().
  365. local_registry_count(Scope) ->
  366. registry_count(Scope, node()).
  367. %% ----- \/ gen_server via module interface --------------------------
  368. %% @private
  369. -spec register_name(Name :: term(), Pid :: pid()) -> yes | no.
  370. register_name({Scope, Name}, Pid) ->
  371. case register(Scope, Name, Pid) of
  372. ok -> yes;
  373. _ -> no
  374. end.
  375. %% @private
  376. -spec unregister_name(Name :: term()) -> term().
  377. unregister_name({Scope, Name}) ->
  378. case unregister(Scope, Name) of
  379. ok -> Name;
  380. _ -> nil
  381. end.
  382. %% @private
  383. -spec whereis_name(Name :: term()) -> pid() | undefined.
  384. whereis_name({Scope, Name}) ->
  385. case lookup(Scope, Name) of
  386. {Pid, _Meta} -> Pid;
  387. undefined -> undefined
  388. end.
  389. %% @private
  390. -spec send(Name :: term(), Message :: term()) -> pid().
  391. send({Scope, Name}, Message) ->
  392. case whereis_name({Scope, Name}) of
  393. undefined ->
  394. {badarg, {{Scope, Name}, Message}};
  395. Pid ->
  396. Pid ! Message,
  397. Pid
  398. end.
  399. %% ----- \/ groups ---------------------------------------------------
  400. %% @doc Returns the list of all members for GroupName in the specified `Scope'.
  401. %%
  402. %% <h2>Examples</h2>
  403. %% <h3>Elixir</h3>
  404. %% ```
  405. %% iex> :syn.join(:devices, "area-1")
  406. %% :ok
  407. %% iex> :syn.members(:devices, "area-1")
  408. %% [{#PID<0.105.0>, :undefined}]
  409. %% '''
  410. %% <h3>Erlang</h3>
  411. %% ```
  412. %% 1> syn:join(devices, "area-1", self()).
  413. %% ok
  414. %% 2> syn:members(devices, "area-1").
  415. %% [{<0.69.0>, undefined}]
  416. %% '''
  417. -spec members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
  418. members(Scope, GroupName) ->
  419. syn_pg:members(Scope, GroupName).
  420. %% @doc Returns whether a `pid()' is a member of GroupName in the specified `Scope'.
  421. -spec is_member(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> boolean().
  422. is_member(Scope, GroupName, Pid) ->
  423. syn_pg:is_member(Scope, GroupName, Pid).
  424. %% @doc Returns the list of all members for GroupName in the specified `Scope' running on the local node.
  425. -spec local_members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
  426. local_members(Scope, GroupName) ->
  427. syn_pg:local_members(Scope, GroupName).
  428. %% @doc Returns whether a `pid()' is a member of GroupName in the specified `Scope' running on the local node.
  429. -spec is_local_member(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> boolean().
  430. is_local_member(Scope, GroupName, Pid) ->
  431. syn_pg:is_local_member(Scope, GroupName, Pid).
  432. %% @equiv join(Scope, GroupName, Pid, undefined)
  433. %% @end
  434. -spec join(Scope :: term(), Name :: term(), Pid :: term()) -> ok | {error, Reason :: term()}.
  435. join(Scope, GroupName, Pid) ->
  436. join(Scope, GroupName, Pid, undefined).
  437. %% @doc Adds a `pid()' with metadata to GroupName in the specified `Scope'.
  438. %%
  439. %% Possible error reasons:
  440. %% <ul>
  441. %% <li>`not_alive': The `pid()' being added is not alive.</li>
  442. %% </ul>
  443. %%
  444. %% A process can join multiple groups. When a process joins a group, Syn will automatically monitor it.
  445. %% A process may join the same group multiple times, for example if you need to update its metadata,
  446. %% though it will still be listed only once in it.
  447. %%
  448. %% <h2>Examples</h2>
  449. %% <h3>Elixir</h3>
  450. %% ```
  451. %% iex> :syn.join(:devices, "area-1", self(), [meta: :one])
  452. %% :ok
  453. %% '''
  454. %% <h3>Erlang</h3>
  455. %% ```
  456. %% 1> syn:join(devices, "area-1", self(), [{meta, one}]).
  457. %% ok
  458. %% '''
  459. -spec join(Scope :: atom(), GroupName :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
  460. join(Scope, GroupName, Pid, Meta) ->
  461. syn_pg:join(Scope, GroupName, Pid, Meta).
  462. %% @doc Removes a `pid()' from GroupName in the specified `Scope'.
  463. %%
  464. %% Possible error reasons:
  465. %% <ul>
  466. %% <li>`not_in_group': The `pid()' is not in GroupName for the specified `Scope'.</li>
  467. %% </ul>
  468. %%
  469. %% You don't need to remove processes that are about to die, since they are monitored by Syn and they will be removed
  470. %% automatically from their groups.
  471. -spec leave(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> ok | {error, Reason :: term()}.
  472. leave(Scope, GroupName, Pid) ->
  473. syn_pg:leave(Scope, GroupName, Pid).
  474. %% @doc Returns the count of all the groups for the specified `Scope'.
  475. %%
  476. %% <h2>Examples</h2>
  477. %% <h3>Elixir</h3>
  478. %% ```
  479. %% iex> :syn.group_count(:users)
  480. %% 321778
  481. %% '''
  482. %% <h3>Erlang</h3>
  483. %% ```
  484. %% 1> syn:group_count(users).
  485. %% 321778
  486. %% '''
  487. -spec group_count(Scope :: atom()) -> non_neg_integer().
  488. group_count(Scope) ->
  489. syn_pg:count(Scope).
  490. %% @doc Returns the count of all the groups for the specified `Scope' which have at least 1 process running on `Node'.
  491. -spec group_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
  492. group_count(Scope, Node) ->
  493. syn_pg:count(Scope, Node).
  494. %% @equiv group_count(Scope, node())
  495. %% @end
  496. -spec local_group_count(Scope :: atom()) -> non_neg_integer().
  497. local_group_count(Scope) ->
  498. group_count(Scope, node()).
  499. %% @doc Returns the group names for the specified `Scope'.
  500. %%
  501. %% The order of the group names is not guaranteed to be the same on all calls.
  502. %%
  503. %% <h2>Examples</h2>
  504. %% <h3>Elixir</h3>
  505. %% ```
  506. %% iex> :syn.group_names(:users)
  507. %% ["area-1", "area-2"]
  508. %% '''
  509. %% <h3>Erlang</h3>
  510. %% ```
  511. %% 1> syn:group_names(users).
  512. %% ["area-1", "area-2"]
  513. %% '''
  514. -spec group_names(Scope :: atom()) -> [GroupName :: term()].
  515. group_names(Scope) ->
  516. syn_pg:group_names(Scope).
  517. %% @doc Returns the group names for the specified `Scope' which have at least 1 process running on `Node'.
  518. %%
  519. %% The order of the group names is not guaranteed to be the same on all calls.
  520. -spec group_names(Scope :: atom(), Node :: node()) -> [GroupName :: term()].
  521. group_names(Scope, Node) ->
  522. syn_pg:group_names(Scope, Node).
  523. %% @equiv group_names(Scope, node())
  524. %% @end
  525. -spec local_group_names(Scope :: atom()) -> [GroupName :: term()].
  526. local_group_names(Scope) ->
  527. group_names(Scope, node()).
  528. %% @doc Publish a message to all group members in the specified `Scope'.
  529. %%
  530. %% `RecipientCount' is the count of the intended recipients.
  531. %%
  532. %% <h2>Examples</h2>
  533. %% <h3>Elixir</h3>
  534. %% ```
  535. %% iex> :syn.join(:users, "area-1", self())
  536. %% :ok
  537. %% iex> :syn.publish(:users, "area-1", :my_message)
  538. %% {:ok,1}
  539. %% iex> flush()
  540. %% Shell got :my_message
  541. %% :ok
  542. %% '''
  543. %% <h3>Erlang</h3>
  544. %% ```
  545. %% 1> syn:join(users, "area-1", self()).
  546. %% ok
  547. %% 2> syn:publish(users, "area-1", my_message).
  548. %% {ok,1}
  549. %% 3> flush().
  550. %% Shell got my_message
  551. %% ok
  552. %% '''
  553. -spec publish(Scope :: atom(), GroupName :: term(), Message :: term()) -> {ok, RecipientCount :: non_neg_integer()}.
  554. publish(Scope, GroupName, Message) ->
  555. syn_pg:publish(Scope, GroupName, Message).
  556. %% @doc Publish a message to all group members running on the local node in the specified `Scope'.
  557. %%
  558. %% Works similarly to {@link publish/3} for local processes.
  559. -spec local_publish(Scope :: atom(), GroupName :: term(), Message :: term()) -> {ok, RecipientCount :: non_neg_integer()}.
  560. local_publish(Scope, GroupName, Message) ->
  561. syn_pg:local_publish(Scope, GroupName, Message).
  562. %% @equiv multi_call(Scope, GroupName, Message, 5000)
  563. %% @end
  564. -spec multi_call(Scope :: atom(), GroupName :: term(), Message :: term()) ->
  565. {
  566. Replies :: [{{pid(), Meta :: term()}, Reply :: term()}],
  567. BadReplies :: [{pid(), Meta :: term()}]
  568. }.
  569. multi_call(Scope, GroupName, Message) ->
  570. multi_call(Scope, GroupName, Message, ?DEFAULT_MULTI_CALL_TIMEOUT_MS).
  571. %% @doc Calls all group members in the specified `Scope' and collects their replies.
  572. %%
  573. %% When this call is issued, all members will receive a tuple in the format:
  574. %%
  575. %% `{syn_multi_call, TestMessage, Caller, Meta}'
  576. %%
  577. %% To reply, every member MUST use the method {@link multi_call_reply/2}.
  578. %%
  579. %% Syn will wait up to the value specified in `Timeout' to receive all replies from the members.
  580. %% The responses will be added to the `Replies' list, while the members that do not reply in time or that crash
  581. %% before sending a reply will be added to the `BadReplies' list.
  582. -spec multi_call(Scope :: atom(), GroupName :: term(), Message :: term(), Timeout :: non_neg_integer()) ->
  583. {
  584. Replies :: [{{pid(), Meta :: term()}, Reply :: term()}],
  585. BadReplies :: [{pid(), Meta :: term()}]
  586. }.
  587. multi_call(Scope, GroupName, Message, Timeout) ->
  588. syn_pg:multi_call(Scope, GroupName, Message, Timeout).
  589. %% @doc Allows a group member to reply to a multi call.
  590. %%
  591. %% See {@link multi_call/4} for info.
  592. -spec multi_call_reply(Caller :: term(), Reply :: term()) -> any().
  593. multi_call_reply(Caller, Reply) ->
  594. syn_pg:multi_call_reply(Caller, Reply).