syn.erl 25 KB

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