syn.erl 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  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 `syn' exposes all of the global Process Registry and Process Group APIs.
  28. %%
  29. %% Syn implement Scopes. A Scope is a way to create a logical overlay network running on top of the Erlang distribution cluster.
  30. %% Nodes that belong to the same Scope will form a "sub-cluster", and will synchronize data between themselves and themselves only.
  31. %%
  32. %% Every node in an Erlang cluster is automatically added to the Scope `default'. It is therefore not mandatory
  33. %% to use scopes, but it is advisable to do so when scalability is a concern, as it is possible to divide
  34. %% an Erlang cluster into sub-clusters which hold specific portions of data.
  35. %%
  36. %% Please note that most of the methods documented here that allow to specify a Scope will raise a
  37. %% `error({invalid_scope, Scope})' if the local node has not been added to the specified Scope or if the Pids
  38. %% passed in as variables are running on a node that has not been added to the specified Scope.
  39. %%
  40. %% @end
  41. %% ===================================================================
  42. -module(syn).
  43. %% API
  44. -export([start/0, stop/0]).
  45. %% scopes
  46. -export([node_scopes/0, add_node_to_scope/1, add_node_to_scopes/1]).
  47. -export([set_event_handler/1]).
  48. %% registry
  49. -export([lookup/1, lookup/2]).
  50. -export([register/2, register/3, register/4]).
  51. -export([unregister/1, unregister/2]).
  52. -export([registry_count/0, registry_count/1, registry_count/2]).
  53. -export([local_registry_count/0, local_registry_count/1]).
  54. %% gen_server via interface
  55. -export([register_name/2, unregister_name/1, whereis_name/1, send/2]).
  56. %% groups
  57. -export([members/1, members/2]).
  58. -export([is_member/2, is_member/3]).
  59. -export([local_members/1, local_members/2]).
  60. -export([is_local_member/2, is_local_member/3]).
  61. -export([join/2, join/3, join/4]).
  62. -export([leave/2, leave/3]).
  63. -export([group_count/0, group_count/1, group_count/2]).
  64. -export([local_group_count/0, local_group_count/1]).
  65. -export([group_names/0, group_names/1, group_names/2]).
  66. -export([local_group_names/0, local_group_names/1]).
  67. -export([publish/2, publish/3]).
  68. -export([local_publish/2, local_publish/3]).
  69. -export([multi_call/2, multi_call/3, multi_call/4, multi_call_reply/2]).
  70. %% API
  71. %% ===================================================================
  72. %% @doc Starts Syn manually.
  73. %%
  74. %% In most cases Syn will be started as one of your application's dependencies,
  75. %% however you may use this helper method to start it manually.
  76. -spec start() -> ok.
  77. start() ->
  78. {ok, _} = application:ensure_all_started(syn),
  79. ok.
  80. %% @doc Stops Syn manually.
  81. -spec stop() -> ok | {error, Reason :: any()}.
  82. stop() ->
  83. application:stop(syn).
  84. %% ----- \/ scopes ---------------------------------------------------
  85. %% @doc Retrieves the Scopes that the node has been added to.
  86. -spec node_scopes() -> [atom()].
  87. node_scopes() ->
  88. syn_sup:node_scopes().
  89. %% @doc Add the local node to the specified `Scope'.
  90. %%
  91. %% <h2>Examples</h2>
  92. %% The following adds the local node to the scope "devices" and then register a process handling a device in that scope:
  93. -spec add_node_to_scope(Scope :: atom()) -> ok.
  94. add_node_to_scope(Scope) ->
  95. syn_sup:add_node_to_scope(Scope).
  96. %% @doc Add the local node to the specified `Scope's.
  97. -spec add_node_to_scopes(Scopes :: [atom()]) -> ok.
  98. add_node_to_scopes(Scopes) ->
  99. lists:foreach(fun(Scope) ->
  100. syn_sup:add_node_to_scope(Scope)
  101. end, Scopes).
  102. %% @doc Sets the handler module.
  103. %%
  104. %% Please see {@link syn_event_handler} for information on callbacks.
  105. %%
  106. %% <h2>Examples</h2>
  107. %% <h3>Elixir</h3>
  108. %% ```
  109. %% iex> :syn.set_event_handler(MyCustomEventHandler)
  110. %% ok
  111. %% '''
  112. %% <h3>Erlang</h3>
  113. %% ```
  114. %% 1> syn:set_event_handler(my_custom_event_handler).
  115. %% ok
  116. %% '''
  117. -spec set_event_handler(module()) -> ok.
  118. set_event_handler(Module) ->
  119. application:set_env(syn, event_handler, Module).
  120. %% ----- \/ registry -------------------------------------------------
  121. %% @doc Looks up a registry entry in the `default' scope.
  122. %%
  123. %% @equiv lookup(default, Name)
  124. %% @end
  125. %%
  126. %% <h2>Examples</h2>
  127. %% <h3>Elixir</h3>
  128. %% ```
  129. %% iex> :syn.register("SN-123-456789", self())
  130. %% :ok
  131. %% iex> :syn.lookup("SN-123-456789")
  132. %% {#PID<0.105.0>, undefined}
  133. %% '''
  134. %% <h3>Erlang</h3>
  135. %% ```
  136. %% 1> syn:register("SN-123-456789", self()).
  137. %% ok
  138. %% 2> syn:lookup(devices, "SN-123-456789").
  139. %% {<0.79.0>, undefined}
  140. %% '''
  141. -spec lookup(Name :: any()) -> {pid(), Meta :: any()} | undefined.
  142. lookup(Name) ->
  143. syn_registry:lookup(Name).
  144. %% @doc Looks up a registry entry in the specified `Scope'.
  145. -spec lookup(Scope :: atom(), Name :: any()) -> {pid(), Meta :: any()} | undefined.
  146. lookup(Scope, Name) ->
  147. syn_registry:lookup(Scope, Name).
  148. %% @doc Registers a process in the `default' scope.
  149. %%
  150. %% @equiv register(default, Name, Pid, undefined)
  151. %% @end
  152. %%
  153. %% <h2>Examples</h2>
  154. %% <h3>Elixir</h3>
  155. %% ```
  156. %% iex> :syn.register("SN-123-456789", self())
  157. %% :ok
  158. %% iex> :syn.lookup("SN-123-456789")
  159. %% {#PID<0.105.0>, undefined}
  160. %% '''
  161. %% <h3>Erlang</h3>
  162. %% ```
  163. %% 1> syn:register("SN-123-456789", self()).
  164. %% ok
  165. %% 2> syn:lookup(devices, "SN-123-456789").
  166. %% {<0.79.0>, undefined}
  167. %% '''
  168. -spec register(Name :: any(), Pid :: pid()) -> ok | {error, Reason :: any()}.
  169. register(Name, Pid) ->
  170. syn_registry:register(Name, Pid).
  171. %% @doc Registers a process with metadata in the `default' scope OR with undefined metadata in the specified `Scope'.
  172. %%
  173. %% Equivalent to`register(default, Name, Pid, Meta)' or `register(Scope, Name, Pid, undefined)'
  174. %% depending on the position of the `pid()' value. See {@link register/4} for more info.
  175. -spec register(NameOrScope :: any(), PidOrName :: any(), MetaOrPid :: any()) -> ok | {error, Reason :: any()}.
  176. register(NameOrScope, PidOrName, MetaOrPid) ->
  177. syn_registry:register(NameOrScope, PidOrName, MetaOrPid).
  178. %% @doc Registers a process with metadata in the specified `Scope'.
  179. %%
  180. %% Possible error reasons:
  181. %% <ul>
  182. %% <li>`taken': name is already registered with another `pid()'.</li>
  183. %% <li>`not_alive': The `pid()' being registered is not alive.</li>
  184. %% </ul>
  185. %%
  186. %% You may re-register a process multiple times, for example if you need to update its metadata.
  187. %% When a process gets registered, Syn will automatically monitor it. You may also register the same process with different names.
  188. %%
  189. %% Processes can also be registered as `gen_server' names, by usage of via-tuples. This way, you can use the `gen_server'
  190. %% API with these tuples without referring to the Pid directly. If you do so, you MUST use a `gen_server' name
  191. %% in format `{Scope, Name}', i.e. your via tuple will look like `{via, syn, {my_scope, <<"process name">>}}'.
  192. %% See here below for examples.
  193. %%
  194. %% <h2>Examples</h2>
  195. %% <h3>Elixir</h3>
  196. %% ```
  197. %% iex> :syn.add_node_to_scope(:devices).
  198. %% :ok
  199. %% iex> :syn.register(:devices, "SN-123-456789", self(), [meta: :one])
  200. %% :ok
  201. %% iex> :syn.lookup(:devices, "SN-123-456789")
  202. %% {#PID<0.105.0>, [meta: :one]}
  203. %% '''
  204. %% ```
  205. %% iex> tuple = {:via, :syn, {:default, <<"your process name">>}}.
  206. %% {:via, :syn, {:default, <<"your process name">>}}
  207. %% iex> GenServer.start_link(__MODULE__, [], name: tuple)
  208. %% {ok, #PID<0.105.0>}
  209. %% iex> GenServer.call(tuple, :your_message).
  210. %% :your_message
  211. %% '''
  212. %% <h3>Erlang</h3>
  213. %% ```
  214. %% 1> syn:add_node_to_scope(devices).
  215. %% ok
  216. %% 2> syn:register(devices, "SN-123-456789", self(), [{meta, one}]).
  217. %% ok
  218. %% 3> syn:lookup(devices, "SN-123-456789").
  219. %% {<0.79.0>, [{meta, one}]}
  220. %% '''
  221. %% ```
  222. %% 1> Tuple = {via, syn, {default, <<"your process name">>}}.
  223. %% {via, syn, {default, <<"your process name">>}}
  224. %% 2> gen_server:start_link(Tuple, your_module, []).
  225. %% {ok, <0.79.0>}
  226. %% 3> gen_server:call(Tuple, your_message).
  227. %% your_message
  228. %% '''
  229. -spec register(Scope :: atom(), Name :: any(), Pid :: pid(), Meta :: any()) -> ok | {error, Reason :: any()}.
  230. register(Scope, Name, Pid, Meta) ->
  231. syn_registry:register(Scope, Name, Pid, Meta).
  232. %% @doc Unregisters a process.
  233. %%
  234. %% @equiv unregister(default, Name)
  235. %% @end
  236. -spec unregister(Name :: any()) -> ok | {error, Reason :: any()}.
  237. unregister(Name) ->
  238. syn_registry:unregister(Name).
  239. %% @doc Unregisters a process.
  240. %%
  241. %% Possible error reasons:
  242. %% <ul>
  243. %% <li>`undefined': name is not registered.</li>
  244. %% <li>`race_condition': the local `pid()' does not correspond to the cluster value. This is a rare occasion.</li>
  245. %% </ul>
  246. %%
  247. %% You don't need to unregister names of processes that are about to die, since they are monitored by Syn
  248. %% and they will be removed automatically. If you manually unregister a process before it dies, the Syn callbacks will not be called.
  249. -spec unregister(Scope :: atom(), Name :: any()) -> ok | {error, Reason :: any()}.
  250. unregister(Scope, Name) ->
  251. syn_registry:unregister(Scope, Name).
  252. %% @doc Returns the count of all registered processes for the `default' scope.
  253. %%
  254. %% @equiv registry_count(default)
  255. %% @end
  256. %%
  257. %% <h2>Examples</h2>
  258. %% <h3>Elixir</h3>
  259. %% ```
  260. %% iex> :syn.registry_count()
  261. %% 512473
  262. %% '''
  263. %% <h3>Erlang</h3>
  264. %% ```
  265. %% 1> syn:registry_count().
  266. %% 512473
  267. %% '''
  268. -spec registry_count() -> non_neg_integer().
  269. registry_count() ->
  270. syn_registry:count().
  271. %% @doc Returns the count of all registered processes for the specified `Scope'.
  272. -spec registry_count(Scope :: atom()) -> non_neg_integer().
  273. registry_count(Scope) ->
  274. syn_registry:count(Scope).
  275. %% @doc Returns the count of all registered processes for the specified `Scope' running on a node.
  276. -spec registry_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
  277. registry_count(Scope, Node) ->
  278. syn_registry:count(Scope, Node).
  279. %% @doc Returns the count of all registered processes for the `default' scope running on the local node.
  280. %%
  281. %% @equiv registry_count(default, node())
  282. %% @end
  283. -spec local_registry_count() -> non_neg_integer().
  284. local_registry_count() ->
  285. syn_registry:local_count().
  286. %% @doc Returns the count of all registered processes for the specified `Scope' running on the local node.
  287. %%
  288. %% @equiv registry_count(Scope, node())
  289. %% @end
  290. -spec local_registry_count(Scope :: atom()) -> non_neg_integer().
  291. local_registry_count(Scope) ->
  292. syn_registry:local_count(Scope).
  293. %% ----- \/ gen_server via module interface --------------------------
  294. -spec register_name(Name :: any(), Pid :: pid()) -> yes | no.
  295. register_name({Scope, Name}, Pid) ->
  296. case syn_registry:register(Scope, Name, Pid) of
  297. ok -> yes;
  298. _ -> no
  299. end.
  300. -spec unregister_name(Name :: any()) -> any().
  301. unregister_name({Scope, Name}) ->
  302. case syn_registry:unregister(Scope, Name) of
  303. ok -> Name;
  304. _ -> nil
  305. end.
  306. -spec whereis_name(Name :: any()) -> pid() | undefined.
  307. whereis_name({Scope, Name}) ->
  308. case syn_registry:lookup(Scope, Name) of
  309. {Pid, _Meta} -> Pid;
  310. undefined -> undefined
  311. end.
  312. -spec send(Name :: any(), Message :: any()) -> pid().
  313. send({Scope, Name}, Message) ->
  314. case whereis_name({Scope, Name}) of
  315. undefined ->
  316. {badarg, {{Scope, Name}, Message}};
  317. Pid ->
  318. Pid ! Message,
  319. Pid
  320. end.
  321. %% ----- \/ groups ---------------------------------------------------
  322. %% @doc Returns the list of all members for GroupName in the `default' Scope.
  323. %%
  324. %% @equiv members(default, GroupName)
  325. %% @end
  326. %%
  327. %% <h2>Examples</h2>
  328. %% <h3>Elixir</h3>
  329. %% ```
  330. %% iex> :syn.join("area-1").
  331. %% :ok
  332. %% iex> :syn.members("area-1").
  333. %% [{#PID<0.105.0>, :undefined}]
  334. %% '''
  335. %% <h3>Erlang</h3>
  336. %% ```
  337. %% 1> syn:join("area-1", self()).
  338. %% ok
  339. %% 2> syn:members("area-1").
  340. %% [{<0.69.0>, undefined}]
  341. %% '''
  342. -spec members(GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
  343. members(GroupName) ->
  344. syn_groups:members(GroupName).
  345. %% @doc Returns the list of all members for GroupName in the specified `Scope'.
  346. %%
  347. %% <h2>Examples</h2>
  348. %% <h3>Elixir</h3>
  349. %% ```
  350. %% iex> :syn.add_node_to_scope(:devices)
  351. %% :ok
  352. %% iex> :syn.join(:devices, "area-1").
  353. %% :ok
  354. %% iex> :syn.members(:devices, "area-1").
  355. %% [{#PID<0.105.0>, :undefined}]
  356. %% '''
  357. %% <h3>Erlang</h3>
  358. %% ```
  359. %% 1> syn:add_node_to_scope(devices)
  360. %% ok
  361. %% 2> syn:join(devices, "area-1", self()).
  362. %% ok
  363. %% 3> syn:members(devices, "area-1").
  364. %% [{<0.69.0>, undefined}]
  365. %% '''
  366. -spec members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
  367. members(Scope, GroupName) ->
  368. syn_groups:members(Scope, GroupName).
  369. %% @doc Returns whether a `pid()' is a member of GroupName in the `default' scope.
  370. %%
  371. %% @equiv is_member(default, GroupName, Pid)
  372. %% @end
  373. -spec is_member(GroupName :: any(), Pid :: pid()) -> boolean().
  374. is_member(GroupName, Pid) ->
  375. syn_groups:is_member(GroupName, Pid).
  376. %% @doc Returns whether a `pid()' is a member of GroupName in the specified `Scope'.
  377. %%
  378. %% This method will raise a `error({invalid_scope, Scope})' if the node has not been added to the specified `Scope'.
  379. -spec is_member(Scope :: atom(), GroupName :: any(), Pid :: pid()) -> boolean().
  380. is_member(Scope, GroupName, Pid) ->
  381. syn_groups:is_member(Scope, GroupName, Pid).
  382. %% @doc Returns the list of all members for GroupName in the `default' scope running on the local node.
  383. %%
  384. %% @equiv local_members(default, GroupName)
  385. %% @end
  386. -spec local_members(GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
  387. local_members(GroupName) ->
  388. syn_groups:local_members(GroupName).
  389. %% @doc Returns the list of all members for GroupName in the specified `Scope' running on the local node.
  390. %%
  391. %% This method will raise a `error({invalid_scope, Scope})' if the node has not been added to the specified `Scope'.
  392. -spec local_members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
  393. local_members(Scope, GroupName) ->
  394. syn_groups:local_members(Scope, GroupName).
  395. %% @doc Returns whether a `pid()' is a member of GroupName in the `default' scope running on the local node.
  396. %%
  397. %% @equiv is_local_member(default, GroupName, Pid)
  398. %% @end
  399. -spec is_local_member(GroupName :: any(), Pid :: pid()) -> boolean().
  400. is_local_member(GroupName, Pid) ->
  401. syn_groups:is_local_member(GroupName, Pid).
  402. %% @doc Returns whether a `pid()' is a member of GroupName in the specified `Scope' running on the local node.
  403. %%
  404. %% This method will raise a `error({invalid_scope, Scope})' if the node has not been added to the specified `Scope'.
  405. -spec is_local_member(Scope :: atom(), GroupName :: any(), Pid :: pid()) -> boolean().
  406. is_local_member(Scope, GroupName, Pid) ->
  407. syn_groups:is_local_member(Scope, GroupName, Pid).
  408. %% @doc Adds a `pid()' to GroupName in the `default' scope.
  409. %%
  410. %% @equiv join(default, GroupName, Pid)
  411. %% @end
  412. %%
  413. %% <h2>Examples</h2>
  414. %% <h3>Elixir</h3>
  415. %% ```
  416. %% iex> :syn.join(:"area-1", self()).
  417. %% :ok
  418. %% '''
  419. %% <h3>Erlang</h3>
  420. %% ```
  421. %% 1> syn:join("area-1", self()).
  422. %% ok
  423. %% '''
  424. -spec join(GroupName :: any(), Pid :: pid()) -> ok | {error, Reason :: any()}.
  425. join(GroupName, Pid) ->
  426. syn_groups:join(GroupName, Pid).
  427. %% @doc Adds a `pid()' with metadata to GroupName in the `default' scope OR with undefined metadata in the specified `Scope'.
  428. -spec join(GroupNameOrScope :: any(), PidOrGroupName :: any(), MetaOrPid :: any()) -> ok | {error, Reason :: any()}.
  429. join(GroupNameOrScope, PidOrGroupName, MetaOrPid) ->
  430. syn_groups:join(GroupNameOrScope, PidOrGroupName, MetaOrPid).
  431. %% @doc Adds a `pid()' with metadata to GroupName in the specified `Scope'.
  432. %%
  433. %% Possible error reasons:
  434. %% <ul>
  435. %% <li>`not_alive': The `pid()' being added is not alive.</li>
  436. %% </ul>
  437. %%
  438. %% A process can join multiple groups. When a process joins a group, Syn will automatically monitor it.
  439. %% A process may join the same group multiple times, for example if you need to update its metadata,
  440. %% though it will still be listed only once in it.
  441. %%
  442. %% <h2>Examples</h2>
  443. %% <h3>Elixir</h3>
  444. %% ```
  445. %% iex> :syn.add_node_to_scope(:devices)
  446. %% :ok
  447. %% iex> :syn.join(:devices, "area-1", self(), [meta: :one]).
  448. %% :ok
  449. %% '''
  450. %% <h3>Erlang</h3>
  451. %% ```
  452. %% 1> syn:add_node_to_scope(devices).
  453. %% ok
  454. %% 2> syn:join(devices, "area-1", self(), [{meta, one}]).
  455. %% ok
  456. %% '''
  457. -spec join(Scope :: atom(), GroupName :: any(), Pid :: pid(), Meta :: any()) -> ok | {error, Reason :: any()}.
  458. join(Scope, GroupName, Pid, Meta) ->
  459. syn_groups:join(Scope, GroupName, Pid, Meta).
  460. %% @doc Removes a `pid()' from GroupName in the `default' Scope.
  461. %%
  462. %% @equiv leave(default, GroupName, Pid)
  463. %% @end
  464. -spec leave(GroupName :: any(), Pid :: pid()) -> ok | {error, Reason :: any()}.
  465. leave(GroupName, Pid) ->
  466. syn_groups:leave(GroupName, Pid).
  467. %% @doc Removes a `pid()' from GroupName in the specified `Scope'.
  468. %%
  469. %% Possible error reasons:
  470. %% <ul>
  471. %% <li>`not_in_group': The `pid()' is not in GroupName for the specified `Scope'.</li>
  472. %% </ul>
  473. %%
  474. %% You don't need to remove processes that are about to die, since they are monitored by Syn and they will be removed
  475. %% automatically from their groups.
  476. -spec leave(Scope :: atom(), GroupName :: any(), Pid :: pid()) -> ok | {error, Reason :: any()}.
  477. leave(Scope, GroupName, Pid) ->
  478. syn_groups:leave(Scope, GroupName, Pid).
  479. %% @doc Returns the count of all the groups for the `default' scope.
  480. %%
  481. %% @equiv group_count(default)
  482. %% @end
  483. %%
  484. %% <h2>Examples</h2>
  485. %% <h3>Elixir</h3>
  486. %% ```
  487. %% iex> :syn.group_count()
  488. %% 321778
  489. %% '''
  490. %% <h3>Erlang</h3>
  491. %% ```
  492. %% 1> syn:group_count().
  493. %% 321778
  494. %% '''
  495. -spec group_count() -> non_neg_integer().
  496. group_count() ->
  497. syn_groups:count().
  498. %% @doc Returns the count of all the groups for the specified `Scope'.
  499. -spec group_count(Scope :: atom()) -> non_neg_integer().
  500. group_count(Scope) ->
  501. syn_groups:count(Scope).
  502. %% @doc Returns the count of all the groups for the specified `Scope' which have at least 1 process running on `Node'.
  503. -spec group_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
  504. group_count(Scope, Node) ->
  505. syn_groups:count(Scope, Node).
  506. %% @doc Returns the count of all the groups which have at least 1 process running on `Node' for the `default' scope.
  507. %%
  508. %% @equiv group_count(default, node())
  509. %% @end
  510. -spec local_group_count() -> non_neg_integer().
  511. local_group_count() ->
  512. syn_groups:local_count().
  513. %% @doc Returns the count of all the groups which have at least 1 process running on `Node' for the specified `Scope'.
  514. %%
  515. %% @equiv group_count(Scope, node())
  516. %% @end
  517. -spec local_group_count(Scope :: atom()) -> non_neg_integer().
  518. local_group_count(Scope) ->
  519. syn_groups:local_count(Scope).
  520. %% @doc Returns the group names for the `default' scope.
  521. %%
  522. %% The order of the group names is not guaranteed to be the same on all calls.
  523. %%
  524. %% @equiv group_names(default)
  525. %% @end
  526. %%
  527. %% <h2>Examples</h2>
  528. %% <h3>Elixir</h3>
  529. %% ```
  530. %% iex> :syn.group_names()
  531. %% ["area-1", "area-2"]
  532. %% '''
  533. %% <h3>Erlang</h3>
  534. %% ```
  535. %% 1> syn:group_names().
  536. %% ["area-1", "area-2"]
  537. %% '''
  538. -spec group_names() -> [GroupName :: term()].
  539. group_names() ->
  540. syn_groups:group_names().
  541. %% @doc Returns the group names for the specified `Scope'.
  542. %%
  543. %% The order of the group names is not guaranteed to be the same on all calls.
  544. -spec group_names(Scope :: atom()) -> [GroupName :: term()].
  545. group_names(Scope) ->
  546. syn_groups:group_names(Scope).
  547. %% @doc Returns the group names for the specified `Scope' which have at least 1 process running on `Node'.
  548. %%
  549. %% The order of the group names is not guaranteed to be the same on all calls.
  550. -spec group_names(Scope :: atom(), Node :: node()) -> [GroupName :: term()].
  551. group_names(Scope, Node) ->
  552. syn_groups:group_names(Scope, Node).
  553. %% @doc Returns the group names which have at least 1 process running on `Node' for the `default' scope.
  554. %%
  555. %% @equiv group_names(default, node())
  556. %% @end
  557. -spec local_group_names() -> [GroupName :: term()].
  558. local_group_names() ->
  559. syn_groups:local_group_names().
  560. %% @doc Returns the group names which have at least 1 process running on `Node' for the specified `Scope'.
  561. %%
  562. %% @equiv group_names(Scope, node())
  563. %% @end
  564. -spec local_group_names(Scope :: atom()) -> [GroupName :: term()].
  565. local_group_names(Scope) ->
  566. syn_groups:local_group_names(Scope).
  567. %% @doc Publish a message to all group members in the `default' scope.
  568. %%
  569. %% @equiv publish(default, GroupName, Message)
  570. %% @end
  571. %%
  572. %% <h2>Examples</h2>
  573. %% <h3>Elixir</h3>
  574. %% ```
  575. %% iex> :syn.join("area-1", self())
  576. %% :ok
  577. %% iex> :syn.publish("area-1", :my_message)
  578. %% {:ok,1}
  579. %% iex> flush().
  580. %% Shell got :my_message
  581. %% :ok
  582. %% '''
  583. %% <h3>Erlang</h3>
  584. %% ```
  585. %% 1> syn:join("area-1", self()).
  586. %% ok
  587. %% 2> syn:publish("area-1", my_message).
  588. %% {ok,1}
  589. %% 3> flush().
  590. %% Shell got my_message
  591. %% ok
  592. %% '''
  593. -spec publish(GroupName :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
  594. publish(GroupName, Message) ->
  595. syn_groups:publish(GroupName, Message).
  596. %% @doc Publish a message to all group members in the Specified scope.
  597. %%
  598. %% `RecipientCount' is the count of the intended recipients.
  599. %%
  600. %% <h2>Examples</h2>
  601. %% <h3>Elixir</h3>
  602. %% ```
  603. %% iex> :syn.add_node_to_scope(:devices)
  604. %% :ok
  605. %% iex> :syn.join(:devices, "area-1", self())
  606. %% :ok
  607. %% iex> :syn.publish(:devices, "area-1", :my_message)
  608. %% {:ok,1}
  609. %% iex> flush().
  610. %% Shell got :my_message
  611. %% :ok
  612. %% '''
  613. %% <h3>Erlang</h3>
  614. %% ```
  615. %% 1> syn:add_node_to_scope(devices).
  616. %% ok
  617. %% 2> syn:join(devices, "area-1", self()).
  618. %% ok
  619. %% 3> syn:publish(devices, "area-1", my_message).
  620. %% {ok,1}
  621. %% 4> flush().
  622. %% Shell got my_message
  623. %% ok
  624. %% '''
  625. -spec publish(Scope :: atom(), GroupName :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
  626. publish(Scope, GroupName, Message) ->
  627. syn_groups:publish(Scope, GroupName, Message).
  628. %% @doc Publish a message to all group members running on the local node in the `default' scope.
  629. %%
  630. %% @equiv local_publish(default, GroupName, Message)
  631. %% @end
  632. -spec local_publish(GroupName :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
  633. local_publish(GroupName, Message) ->
  634. syn_groups:local_publish(GroupName, Message).
  635. %% @doc Publish a message to all group members running on the local node in the specified `Scope'.
  636. %%
  637. %% `RecipientCount' is the count of the intended recipients.
  638. -spec local_publish(Scope :: atom(), GroupName :: any(), Message :: any()) -> {ok, RecipientCount :: non_neg_integer()}.
  639. local_publish(Scope, GroupName, Message) ->
  640. syn_groups:local_publish(Scope, GroupName, Message).
  641. %% @doc Calls all group members node in the `default' scope and collects their replies.
  642. %%
  643. %% @equiv multi_call(default, GroupName, Message, 5000)
  644. %% @end
  645. -spec multi_call(GroupName :: any(), Message :: any()) ->
  646. {[{pid(), Reply :: any()}], [BadPid :: pid()]}.
  647. multi_call(GroupName, Message) ->
  648. syn_groups:multi_call(GroupName, Message).
  649. %% @doc Calls all group members running in the specified `Scope' and collects their replies.
  650. %%
  651. %% @equiv multi_call(Scope, GroupName, Message, 5000)
  652. %% @end
  653. -spec multi_call(Scope :: atom(), GroupName :: any(), Message :: any()) ->
  654. {
  655. Replies :: [{{pid(), Meta :: term()}, Reply :: term()}],
  656. BadReplies :: [{pid(), Meta :: term()}]
  657. }.
  658. multi_call(Scope, GroupName, Message) ->
  659. syn_groups:multi_call(Scope, GroupName, Message).
  660. %% @doc Calls all group members running in the specified `Scope' and collects their replies.
  661. %%
  662. %% When this call is issued, all members will receive a tuple in the format:
  663. %%
  664. %% `{syn_multi_call, CallerPid, Message}'
  665. %%
  666. %% To reply, every member MUST use the method {@link multi_call_reply/2}.
  667. %%
  668. %% Syn will wait up to the value specified in `Timeout' to receive all replies from the members.
  669. %% The responses will be added to the `Replies' list, while the members that do not reply in time or that crash
  670. %% before sending a reply will be added to the `BadReplies' list.
  671. -spec multi_call(Scope :: atom(), GroupName :: any(), Message :: any(), Timeout :: non_neg_integer()) ->
  672. {
  673. Replies :: [{{pid(), Meta :: term()}, Reply :: term()}],
  674. BadReplies :: [{pid(), Meta :: term()}]
  675. }.
  676. multi_call(Scope, GroupName, Message, Timeout) ->
  677. syn_groups:multi_call(Scope, GroupName, Message, Timeout).
  678. %% @doc Allows a group member to reply to a multi call.
  679. %%
  680. %% See {@link multi_call/4} for info.
  681. -spec multi_call_reply(CallerPid :: pid(), Reply :: any()) -> {syn_multi_call_reply, pid(), Reply :: any()}.
  682. multi_call_reply(CallerPid, Reply) ->
  683. syn_groups:multi_call_reply(CallerPid, Reply).