syn (syn v3.3.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.

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.

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.

Adds a pid() with metadata to GroupName in the specified Scope.

Removes a pid() from GroupName in the specified Scope.

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.

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.

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.

Link to this section Functions

Link to this function

add_node_to_scopes(Scopes)

View Source

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:

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])
  :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
Link to this function

group_count(Scope, Node)

View Source

Specs

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.

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"]
Link to this function

group_names(Scope, Node)

View Source

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.

The order of the group names is not guaranteed to be the same on all calls.
Link to this function

is_local_member(Scope, GroupName, Pid)

View Source

Specs

is_local_member(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> boolean().
Returns whether a pid() is a member of GroupName in the specified Scope running on the local node.
Link to this function

is_member(Scope, GroupName, Pid)

View Source

Specs

is_member(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> boolean().
Returns whether a 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).

Link to this function

join(Scope, GroupName, Pid, Meta)

View Source

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.

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: The pid() being added is not alive.
  • not_self: the method is being called from a process other than self(), but strict_mode is enabled.

Examples

Elixir

  iex> :syn.join(:devices, "area-1", self(), [meta: :one])
  :ok

Erlang

  1> syn:join(devices, "area-1", self(), [{meta, one}]).
  ok
Link to this function

leave(Scope, GroupName, Pid)

View Source

Specs

leave(Scope :: atom(), GroupName :: term(), Pid :: pid()) -> ok | {error, Reason :: term()}.

Removes a pid() from GroupName in the specified Scope.

Possible error reasons:
  • not_in_group: The pid() 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.
Link to this function

local_group_count(Scope)

View Source

Specs

local_group_count(Scope :: atom()) -> non_neg_integer().

Equivalent to group_count(Scope, node()).

Link to this function

local_group_names(Scope)

View Source

Specs

local_group_names(Scope :: atom()) -> [GroupName :: term()].

Equivalent to group_names(Scope, node()).

Link to this function

local_member_count(Scope, GroupName)

View Source

Specs

local_member_count(Scope :: atom(), GroupName :: term()) -> non_neg_integer().

Equivalent to member_count(Scope, GroupName, node()).

Link to this function

local_members(Scope, GroupName)

View Source

Specs

local_members(Scope :: atom(), GroupName :: term()) -> [{Pid :: pid(), Meta :: term()}].
Returns the list of all members for GroupName in the specified Scope running on the local node.
Link to this function

local_publish(Scope, GroupName, Message)

View Source

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.

Works similarly to publish/3 for local processes.
Link to this function

local_registry_count(Scope)

View Source

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}
Link to this function

member(Scope, GroupName, Pid)

View Source

Specs

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}
Link to this function

member_count(Scope, GroupName)

View Source

Specs

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")
  512473

Erlang

  1> syn:member_count(devices, "abc123").
  512473
Link to this function

member_count(Scope, GroupName, Node)

View Source

Specs

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.
Link to this function

members(Scope, GroupName)

View Source

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", 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}]
Link to this function

multi_call(Scope, GroupName, Message)

View Source

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).

Link to this function

multi_call(Scope, GroupName, Message, Timeout)

View Source

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.

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.
Link to this function

multi_call_reply(Caller, Reply)

View Source

Specs

multi_call_reply(Caller :: term(), Reply :: term()) -> any().

Allows a group member to reply to a multi call.

See multi_call/4 for info.

Specs

node_scopes() -> [atom()].
Retrieves the Scopes that the node has been added to.
Link to this function

publish(Scope, GroupName, Message)

View Source

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
Link to this function

register(Scope, Name, Pid)

View Source

Specs

register(Scope :: atom(), Name :: term(), Pid :: term()) -> ok | {error, Reason :: term()}.

Equivalent to register(Scope, Name, Pid, undefined).

Link to this function

register(Scope, Name, Pid, Meta)

View Source

Specs

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: The pid() being registered is not alive.
  • taken: name is already registered with another pid().
  • not_self: the method is being called from a process other than self(), but strict_mode is 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_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
Link to this function

registry_count(Scope, Node)

View Source

Specs

registry_count(Scope :: atom(), Node :: node()) -> non_neg_integer().
Returns the count of all registered processes for the specified Scope running on a node.
Link to this function

set_event_handler(Module)

View Source

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()}.
Stops Syn manually.
Link to this function

subcluster_nodes(Manager, Scope)

View Source

Specs

subcluster_nodes(Manager :: registry | pg, Scope :: atom()) -> [node()].
Returns the nodes of the subcluster for the specified Scope.

Specs

unregister(Scope :: atom(), Name :: term()) -> ok | {error, Reason :: term()}.

Unregisters a process from specified Scope.

Possible error reasons:
  • undefined: name is not registered.
  • race_condition: the local pid() 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.
Link to this function

update_member(Scope, GroupName, Pid, Fun)

View Source

Specs

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: The pid() 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}}
Link to this function

update_registry(Scope, Name, Fun)

View Source

Specs

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, "area-1", 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}}