syn (syn v3.0.0) View Source
Exposes 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.
Quickstart
Registry
Elixir
iex> :syn.add_node_to_scopes([:users])
:ok
iex> pid = self()
#PID<0.105.0>
iex> :syn.register(:users, "hedy", pid)
:ok
iex> :syn.lookup(:users, "hedy")
{#PID<0.105.0>,:undefined}
iex> :syn.register(:users, "hedy", pid, [city: "Milan"])
:ok
iex> :syn.lookup(:users, "hedy")
{#PID<0.105.0>,[city: "Milan"]}
iex> :syn.registry_count(:users)
1
Erlang
1> syn:add_node_to_scopes([users]).
ok
2> Pid = self().
<0.93.0>
3> syn:register(users, "hedy", Pid).
ok
4> syn:lookup(users, "hedy").
{<0.93.0>,undefined}
5> syn:register(users, "hedy", Pid, [{city, "Milan"}]).
ok
6> syn:lookup(users, "hedy").
{<0.93.0>,[{city, "Milan"}]}
7> syn:registry_count(users).
1
Process Groups
Elixir
iex> :syn.add_node_to_scopes([:users])
:ok
iex> pid = self()
#PID<0.88.0>
iex> :syn.join(:users, {:italy, :lombardy}, pid)
:ok
iex> :syn.members(:users, {:italy, :lombardy})
[{#PID<0.88.0>,:undefined}]
iex> :syn.is_member(:users, {:italy, :lombardy}, pid)
true
iex> :syn.publish(:users, {:italy, :lombardy}, "hello lombardy!")
{:ok,1}
iex> flush()
Shell got "hello lombardy!"
ok
Erlang
1> syn:add_node_to_scopes([users]).
ok
2> Pid = self().
<0.88.0>
3> syn:join(users, {italy, lombardy}, Pid).
ok
4> syn:members(users, {italy, lombardy}).
[{<0.88.0>,undefined}]
5> syn:is_member(users, {italy, lombardy}, Pid).
true
6> syn:publish(users, {italy, lombardy}, "hello lombardy!").
{ok,1}
7> flush().
Shell got "hello lombardy!"
ok
Link to this section Summary
Functions
Add the local node to the specified Scopes
.
Returns the count of all the groups for the specified Scope
.
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
.
pid()
is a member of GroupName in the specified Scope
running on the local node.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()).
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 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.
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
.
Scope
running on a node.Sets the handler module.
Starts Syn manually.
Scope
.Unregisters a process from specified Scope
.
Link to this section Functions
Specs
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:
Elixir
config :syn,
scopes: [:devices, :users]
Erlang
{syn, [
{scopes, [:devices, :users]}
]}
Examples
Elixir
iex> :syn.add_node_to_scopes([:devices])
:ok
Erlang
1> syn:add_node_to_scopes([devices]).
ok
Specs
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)
321778
Erlang
1> syn:group_count(users).
321778
Specs
group_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
Scope
which have at least 1 process running on Node
.
Specs
group_names(Scope :: atom()) -> [GroupName :: term()].
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"]
Specs
group_names(Scope :: atom(), Node :: node()) -> [GroupName :: term()].
Returns the group names for the specified Scope
which have at least 1 process running on Node
.
Specs
is_local_member(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> boolean().
pid()
is a member of GroupName in the specified Scope
running on the local node.
Specs
is_member(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> boolean().
pid()
is a member of GroupName in the specified Scope
.
Specs
join(Scope :: term(), Name :: term(), Pid :: term()) -> ok | {error, Reason :: term()}.
Equivalent to join(Scope, GroupName, Pid, undefined).
Specs
join(Scope :: atom(), GroupName :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
Adds a pid()
with metadata to GroupName in the specified Scope
.
not_alive
: Thepid()
being added is not alive.
A process can join multiple groups. When a process joins a group, Syn will automatically monitor it. A process may join the same group multiple times, for example if you need to update its metadata, though it will still be listed only once in it.
Examples
Elixir
iex> :syn.join(:devices, "area-1", self(), [meta: :one])
:ok
Erlang
1> syn:join(devices, "area-1", self(), [{meta, one}]).
ok
Specs
leave(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> ok | {error, Reason :: term()}.
Removes a pid()
from GroupName in the specified Scope
.
not_in_group
: Thepid()
is not in GroupName for the specifiedScope
.
Specs
local_group_count(Scope :: atom()) -> non_neg_integer().
Equivalent to group_count(Scope, node()).
Specs
local_group_names(Scope :: atom()) -> [GroupName :: term()].
Equivalent to group_names(Scope, node()).
Specs
local_members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
Scope
running on the local node.
Specs
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
.
publish/3
for local processes.
Specs
local_registry_count(Scope :: atom()) -> non_neg_integer().
Equivalent to registry_count(Scope, node()).
Specs
lookup(Scope :: atom(), Name :: term()) -> {pid(), Meta :: term()} | undefined.
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}
Specs
members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
Returns the list of all members for GroupName in the specified Scope
.
Examples
Elixir
iex> :syn.join(:devices, "area-1")
: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}]
Specs
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).
Specs
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
.
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.
Specs
multi_call_reply(Caller :: term(), Reply :: term()) -> any().
Allows a group member to reply to a multi call.
Seemulti_call/4
for info.
Specs
node_scopes() -> [atom()].
Specs
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
:ok
Erlang
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
Specs
register(Scope :: atom(), Name :: term(), Pid :: term()) -> ok | {error, Reason :: term()}.
Equivalent to register(Scope, Name, Pid, undefined).
Specs
register(Scope :: atom(), Name :: term(), Pid :: pid(), Meta :: term()) -> ok | {error, Reason :: term()}.
Registers a process with metadata in the specified Scope
.
taken
: name is already registered with anotherpid()
.not_alive
: Thepid()
being registered is not alive.
You may re-register a process multiple times, for example if you need to update its metadata. When a process gets registered, Syn will automatically monitor it. You may also register the same process with different names.
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}
, i.e. your via tuple will look like {via, syn, {my_scope, <<"process name">>}}
. 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_message
Erlang
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
Specs
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)
512473
Erlang
1> syn:registry_count(devices).
512473
Specs
registry_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
Scope
running on a node.
Specs
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: MyCustomEventHandler
Erlang
{syn, [
{event_handler, my_custom_event_handler}
]}
Examples
Elixir
iex> :syn.set_event_handler(MyCustomEventHandler)
ok
Erlang
1> syn:set_event_handler(my_custom_event_handler).
ok
Specs
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.Specs
stop() -> ok | {error, Reason :: term()}.
Specs
subcluster_nodes(Manager :: registry | pg, Scope :: atom()) -> [node()].
Scope
.
Specs
unregister(Scope :: atom(), Name :: term()) -> ok | {error, Reason :: term()}.
Unregisters a process from specified Scope
.
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.