syn (syn v3.4.0)
View SourceExposes all of the global Process Registry and Process Group APIs.
Syn implement Scopes. You may think of Scopes such as database tables, so a set of data elements, but that's where the analogy ends.
A Scope is a way to create a namespaced, logical overlay network running on top of the Erlang distribution cluster. Nodes that belong to the same Scope will form a subcluster: they will synchronize data between themselves, and themselves only.
For instance, you may have nodes in your Erlang cluster that need to handle connections to users, and other nodes that need to handle connections to physical devices. One approach is to create two Scopes: users and devices, where you can register your different types of connections.
Scopes are therefore a way to properly namespace your logic, but they also allow to build considerably larger scalable architectures, as it is possible to divide an Erlang cluster into subclusters which hold specific portions of data.
Please note any of the methods documented here will raise:
- An
error({invalid_scope, Scope})if the local node has not been added to the specified Scope. - An
error({invalid_remote_scope, Scope, RemoteNode})if the Pid passed in as variable is running on a node that has not been added to the specified Scope, or if the remote scope process is temporarily down.
Summary
Functions
Add the local node to the specified Scopes.
Returns the count of all the groups for the specified Scope.
Returns the count of all the groups for the specified Scope which have at least 1 process running on Node.
Returns the group names for the specified Scope.
Returns the group names for the specified Scope which have at least 1 process running on Node.
Returns whether a pid() is a member of GroupName in the specified Scope running on the local node.
Returns whether a pid() is a member of GroupName in the specified Scope.
Equivalent to join(Scope, GroupName, Pid, undefined).
Adds a pid() with metadata to GroupName in the specified Scope.
Removes a pid() from GroupName in the specified Scope.
Equivalent to group_count(Scope, node()).
Equivalent to group_names(Scope, node()).
Returns the list of all members for GroupName in the specified Scope running on the local node.
Publish a message to all group members running on the local node in the specified Scope.
Equivalent to registry_count(Scope, node()).
Looks up a registry entry in the specified Scope.
Returns the member for GroupName in the specified Scope.
Returns the count of all members for the specified Scope and GroupName.
Returns the count of all members for the specified Scope and GroupName which have at least 1 process running on Node.
Returns the list of all members for GroupName in the specified Scope.
Calls all group members in the specified Scope and collects their replies.
Allows a group member to reply to a multi call.
Retrieves the Scopes that the node has been added to.
Publish a message to all group members in the specified Scope.
Equivalent to register(Scope, Name, Pid, undefined).
Registers a process with metadata in the specified Scope.
Returns the count of all registered processes for the specified Scope.
Returns the count of all registered processes for the specified Scope running on a node.
Sets the handler module.
Starts Syn manually.
Stops Syn manually.
Returns the nodes of the subcluster for the specified Scope.
Unregisters a process from specified Scope.
Updates the GroupName member metadata in the specified Scope.
Updates the registered Name metadata in the specified Scope.
Functions
-spec add_node_to_scopes(Scopes :: [atom()]) -> ok.
Add the local node to the specified Scopes.
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 with the key scopes. In this latter case, you're probably best off using an application configuration file:
You only need to add a node to a scope once.
Elixir
config :syn,
scopes: [:devices, :users]Erlang
{syn, [
{scopes, [devices, users]}
]}Examples
Elixir
iex> :syn.add_node_to_scopes([:devices])
:okErlang
1> syn:add_node_to_scopes([devices]).
ok
-spec group_count(Scope :: atom()) -> non_neg_integer().
Returns the count of all the groups for the specified Scope.
Examples
Elixir
iex> :syn.group_count(:users)
321778Erlang
1> syn:group_count(users).
321778
-spec group_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
Returns the count of all the groups for the specified Scope which have at least 1 process running on Node.
Returns the group names for the specified Scope.
The order of the group names is not guaranteed to be the same on all calls.
Examples
Elixir
iex> :syn.group_names(:users)
["area-1", "area-2"]Erlang
1> syn:group_names(users).
["area-1", "area-2"]
Returns the group names for the specified Scope which have at least 1 process running on Node.
The order of the group names is not guaranteed to be the same on all calls.
Returns whether a pid() is a member of GroupName in the specified Scope running on the local node.
Returns whether a pid() is a member of GroupName in the specified Scope.
Equivalent to join(Scope, GroupName, Pid, undefined).
-spec join(Scope :: atom(), GroupName :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
Adds a pid() with metadata to GroupName in the specified Scope.
A process can join multiple groups. A process may also join the same group multiple times, for example if you need to update its metadata, however it is recommended to be aware of the implications of updating metadata, see the strict_mode option for more information.
If you want to update a process' metadata by modifying its existing one, you may consider using update_member/4 instead.
When a process joins a group, Syn will automatically monitor it.
Possible error reasons:
not_alive: Thepid()being added is not alive.not_self: the method is being called from a process other thanself(), butstrict_modeis enabled.
Examples
Elixir
iex> :syn.join(:devices, "area-1", self(), [meta: :one])
:okErlang
1> syn:join(devices, "area-1", self(), [{meta, one}]).
ok
Removes a pid() from GroupName in the specified Scope.
Possible error reasons:
not_in_group: Thepid()is not in GroupName for the specified Scope.
You don't need to remove processes that are about to die, since they are monitored by Syn and they will be removed automatically from their groups.
-spec local_group_count(Scope :: atom()) -> non_neg_integer().
Equivalent to group_count(Scope, node()).
Equivalent to group_names(Scope, node()).
-spec local_member_count(Scope :: atom(), GroupName :: term()) -> non_neg_integer().
Equivalent to member_count(Scope, GroupName, node()).
Returns the list of all members for GroupName in the specified Scope running on the local node.
-spec local_publish(Scope :: atom(), GroupName :: term(), Message :: term()) -> {ok, RecipientCount :: non_neg_integer()}.
Publish a message to all group members running on the local node in the specified Scope.
Works similarly to publish/3 for local processes.
-spec local_registry_count(Scope :: atom()) -> non_neg_integer().
Equivalent to registry_count(Scope, node()).
Looks up a registry entry in the specified Scope.
Examples
Elixir
iex> :syn.register(:devices, "SN-123-456789", self())
:ok
iex> :syn.lookup(:devices, "SN-123-456789")
{#PID<0.105.0>, undefined}Erlang
1> syn:register(devices, "SN-123-456789", self()).
ok
2> syn:lookup(devices, "SN-123-456789").
{<0.79.0>, undefined}
-spec member(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> {Pid :: pid(), Meta :: term()} | undefined.
Returns the member for GroupName in the specified Scope.
Examples
Elixir
iex> :syn.join(:devices, "area-1", self(), :meta)
:ok
iex> :syn.member(:devices, "area-1", self())
{#PID<0.105.0>, :meta}Erlang
1> syn:join(devices, "area-1", self(), meta).
ok
2> syn:member(devices, "area-1", self()).
{<0.69.0>, meta}
-spec member_count(Scope :: atom(), GroupName :: term()) -> non_neg_integer().
Returns the count of all members for the specified Scope and GroupName.
Examples
Elixir
iex> :syn.member_count(:devices, "abc123")
512473Erlang
1> syn:member_count(devices, "abc123").
512473
-spec member_count(Scope :: atom(), GroupName :: term(), Node :: node()) -> non_neg_integer().
Returns the count of all members for the specified Scope and GroupName which have at least 1 process running on Node.
Returns the list of all members for GroupName in the specified Scope.
Examples
Elixir
iex> :syn.join(:devices, "area-1", self())
:ok
iex> :syn.members(:devices, "area-1")
[{#PID<0.105.0>, :undefined}]Erlang
1> syn:join(devices, "area-1", self()).
ok
2> syn:members(devices, "area-1").
[{<0.69.0>, undefined}]
-spec multi_call(Scope :: atom(), GroupName :: term(), Message :: term()) -> {Replies :: [{{pid(), Meta :: term()}, Reply :: term()}], BadReplies :: [{pid(), Meta :: term()}]}.
Equivalent to multi_call(Scope, GroupName, Message, 5000).
-spec multi_call(Scope :: atom(), GroupName :: term(), Message :: term(), Timeout :: non_neg_integer()) -> {Replies :: [{{pid(), Meta :: term()}, Reply :: term()}], BadReplies :: [{pid(), Meta :: term()}]}.
Calls all group members in the specified Scope and collects their replies.
When this call is issued, all members will receive a tuple in the format:
{syn_multi_call, TestMessage, Caller, Meta}
To reply, every member MUST use the method multi_call_reply/2.
Syn will wait up to the value specified in Timeout to receive all replies from the members. The responses will be added to the Replies list, while the members that do not reply in time or that crash before sending a reply will be added to the BadReplies list.
Allows a group member to reply to a multi call.
See multi_call/4 for info.
-spec node_scopes() -> [atom()].
Retrieves the Scopes that the node has been added to.
-spec publish(Scope :: atom(), GroupName :: term(), Message :: term()) -> {ok, RecipientCount :: non_neg_integer()}.
Publish a message to all group members in the specified Scope.
RecipientCount is the count of the intended recipients.
Examples
Elixir
iex> :syn.join(:users, "area-1", self())
:ok
iex> :syn.publish(:users, "area-1", :my_message)
{:ok,1}
iex> flush()
Shell got :my_message
:okErlang
1> syn:join(users, "area-1", self()).
ok
2> syn:publish(users, "area-1", my_message).
{ok,1}
3> flush().
Shell got my_message
ok
Equivalent to register(Scope, Name, Pid, undefined).
-spec register(Scope :: atom(), Name :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
Registers a process with metadata in the specified Scope.
You may register the same process with different names. You may also re-register a process multiple times, for example if you need to update its metadata, however it is recommended to be aware of the implications of updating metadata, see the strict_mode option for more information.
If you want to update a process' metadata by modifying its existing one, you may consider using update_registry/3 instead.
When a process gets registered, Syn will automatically monitor it.
Possible error reasons:
not_alive: Thepid()being registered is not alive.taken: name is already registered with anotherpid().not_self: the method is being called from a process other thanself(), butstrict_modeis enabled.
Examples
Elixir
iex> :syn.register(:devices, "SN-123-456789", self(), [meta: :one])
:ok
iex> :syn.lookup(:devices, "SN-123-456789")
{#PID<0.105.0>, [meta: :one]}Erlang
1> syn:register(devices, "SN-123-456789", self(), [{meta, one}]).
ok
2> syn:lookup(devices, "SN-123-456789")
{<0.105.0>,[{meta, one}]}Processes can also be registered as gen_server names, by usage of via-tuples. This way, you can use the gen_server API with these tuples without referring to the Pid directly. If you do so, you MUST use a gen_server name in format:
{Scope, Name}or{Scope, Name, Meta}
i.e. your via tuple will look like {via, syn, {my_scope, <<"process name">>}} or, with meta, {via, syn, {my_scope, <<"process name">>, process_meta}}. See here below for examples.
Examples
Elixir
iex> tuple = {:via, :syn, {:devices, "SN-123-456789"}}.
{:via, :syn, {:devices, "SN-123-456789"}}
iex> GenServer.start_link(__MODULE__, [], name: tuple)
{ok, #PID<0.105.0>}
iex> GenServer.call(tuple, :your_message)
:your_messageErlang
1> Tuple = {via, syn, {devices, "SN-123-456789"}}.
{via, syn, {devices, "SN-123-456789"}}
2> gen_server:start_link(Tuple, your_module, []).
{ok, <0.79.0>}
3> gen_server:call(Tuple, your_message).
your_message
-spec registry_count(Scope :: atom()) -> non_neg_integer().
Returns the count of all registered processes for the specified Scope.
Examples
Elixir
iex> :syn.registry_count(:devices)
512473Erlang
1> syn:registry_count(devices).
512473
-spec registry_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
Returns the count of all registered processes for the specified Scope running on a node.
-spec set_event_handler(module()) -> ok.
Sets the handler module.
Please see syn_event_handler for information on callbacks.
There are 2 ways to set a handler module. One is by using this method, the other is to set the environment variable syn with the key event_handler. In this latter case, you're probably best off using an application configuration file:
Elixir
config :syn,
event_handler: MyCustomEventHandlerErlang
{syn, [
{event_handler, my_custom_event_handler}
]}Examples
Elixir
iex> :syn.set_event_handler(MyCustomEventHandler)
okErlang
1> syn:set_event_handler(my_custom_event_handler).
ok
-spec start() -> ok.
Starts Syn manually.
In most cases Syn will be started as one of your application's dependencies, however you may use this helper method to start it manually.
-spec stop() -> ok | {error, Reason :: term()}.
Stops Syn manually.
Returns the nodes of the subcluster for the specified Scope.
Unregisters a process from specified Scope.
Possible error reasons:
undefined: name is not registered.race_condition: the localpid()does not correspond to the cluster value, so Syn will not succeed unregistering the value and will wait for the cluster to synchronize. This is a rare occasion.
You don't need to unregister names of processes that are about to die, since they are monitored by Syn and they will be removed automatically.
-spec update_member(Scope :: atom(), GroupName :: term(), Pid :: pid(), Fun :: function()) -> {ok, {Pid :: pid(), Meta :: term()}} | {error, Reason :: term()}.
Updates the GroupName member metadata in the specified Scope.
Atomically calls Fun with the current metadata, and stores the return value as new metadata. It is recommended to be aware of the implications of updating metadata, see the strict_mode option for more information.
Possible error reasons:
undefined: Thepid()cannot be found in GroupName.
Note: an error in the update fun will be raised in the calling process.
Examples
Elixir
iex> :syn.join(:devices, "area-1", self(), 10)
:ok
iex> :syn.update_member(:devices, "area-1", self(), fn existing_meta -> existing_meta * 2 end)
{:ok, {#PID<0.105.0>, 20}}Erlang
1> syn:join(devices, "area-1", self(), 10).
ok
2> syn:update_member(devices, "area-1", self(), fun(ExistingMeta) -> ExistingMeta * 2 end).
{ok, {<0.69.0>, 20}}
-spec update_registry(Scope :: atom(), Name :: term(), Fun :: function()) -> {ok, {Pid :: pid(), Meta :: term()}} | {error, Reason :: term()}.
Updates the registered Name metadata in the specified Scope.
Atomically calls Fun with the current metadata, and stores the return value as new metadata. It is recommended to be aware of the implications of updating metadata, see the strict_mode option for more information.
Possible error reasons:
undefined: The Name cannot be found.
Note: an error in the update fun will be raised in the calling process.
Examples
Elixir
iex> :syn.register(:devices, "SN-123-456789", self(), 10)
:ok
iex> :syn.update_registry(:devices, "SN-123-456789", fn _pid, existing_meta -> existing_meta * 2 end)
{:ok, {#PID<0.105.0>, 20}}Erlang
1> syn:register(devices, "SN-123-456789", self(), 10).
ok
2> syn:update_registry(devices, "SN-123-456789", fun(_Pid, ExistingMeta) -> ExistingMeta * 2 end).
{ok, {<0.69.0>, 20}}