syn_event_handler behaviour (syn v3.3.0) View Source
Defines Syn's callbacks.
You can specify the callback module with syn:set_event_handler/1
. In your module you need to specify the behavior syn_event_handler
and implement the callbacks. All callbacks are optional, so you just need to define the ones you need.
All of the callbacks, except for resolve_registry_conflict/4
, are called on all the nodes of the cluster. This allows you to receive events for the processes running on nodes that get shut down, or in case of net splits.
Reason
in the callbacks can be:normal
for expected operations.- Crash reasons when processes die (for
on_process_unregistered/5
andon_process_left/5
). {syn_remote_scope_node_up, Scope, Node}
foron_process_registered/5
andon_process_joined/5
when the callbacks are called due to nodes syncing.{syn_remote_scope_node_down, Scope, Node}
foron_process_unregistered/5
andon_process_left/5
when the callbacks are called due to nodes disconnecting.syn_conflict_resolution
foron_process_registered/5
andon_process_unregistered/5
during registry conflict resolution.undefined
foron_process_unregistered/5
andon_process_left/5
when the processes died while the scope process had crashed.
While all callbacks do not have a direct effect on Syn (their return value is ignored), a special case is the callback resolve_registry_conflict/4
. If specified, this is the method that will be used to resolve registry conflicts when detected.
In case of net splits or race conditions, a specific name might get registered simultaneously on two different nodes. When this happens, the cluster experiences a registry naming conflict.
Syn will resolve this Process Registry conflict by choosing a single process. By default, Syn keeps track of the time when a registration takes place witherlang:system_time/0
, compares values between conflicting processes and:- Keeps the one with the higher value (the process that was registered more recently).
- Kills the other process by sending a kill signal with
exit(Pid, {syn_resolve_kill, Name, Meta})
.
This is a very simple mechanism that can be imprecise, as system clocks are not perfectly aligned in a cluster. If something more elaborate is desired, or if you do not want the discarded process to be killed (i.e. to perform a graceful shutdown), you MAY specify a custom handler that implements the resolve_registry_conflict/4
callback. To this effect, you may also store additional data to resolve conflicts in the Meta
value, since it will be passed into the callback for both of the conflicting processes.
If implemented, this method MUST return the pid()
of the process that you wish to keep. The other process will not be killed, so you will have to decide what to do with it. If the custom conflict resolution method does not return one of the two Pids, or if the method crashes, none of the Pids will be killed and the conflicting name will be freed.
Important Note: the conflict resolution method will be called on the two nodes where the conflicting processes are running on. Therefore, this method MUST be defined in the same way across all nodes of the cluster and have the same effect regardless of the node it is run on, or you will experience unexpected results.
Examples
The following callback module implements theon_process_unregistered/4
and the on_process_left/4
callbacks.Elixir
defmodule MyCustomEventHandler do
@behaviour :syn_event_handler
@impl true
def on_process_unregistered(scope, name, pid, meta, reason) do
end
@impl true
def on_process_left(scope, group_name, pid, meta, reason) do
end
end
Erlang
-module(my_custom_event_handler).
-behaviour(syn_event_handler).
-export([on_process_unregistered/4]).
-export([on_group_process_exit/4]).
-spec on_process_unregistered(
Scope :: atom(),
Name :: term(),
Pid :: pid(),
Meta :: term(),
Reason :: atom()
) -> term().
on_process_unregistered(Scope, Name, Pid, Meta, Reason) ->
ok.
-spec on_process_left(
Scope :: atom(),
GroupName :: term(),
Pid :: pid(),
Meta :: term(),
Reason :: atom()
) -> term().
on_process_left(Scope, GroupName, Pid, Meta, Reason) ->
ok.
Link to this section Summary
Link to this section Callbacks
Specs
on_group_process_updated(Scope :: atom(), GroupName :: term(), Pid :: pid(), Meta :: term(), Reason :: atom()) -> any().
Specs
on_group_process_updated(Scope :: atom(), GroupName :: term(), Pid :: pid(), PreviousMeta :: term(), Meta :: term(), Reason :: atom()) -> any().
Specs
on_process_joined(Scope :: atom(), GroupName :: term(), Pid :: pid(), Meta :: term(), Reason :: atom()) -> any().
Specs
on_process_left(Scope :: atom(), GroupName :: term(), Pid :: pid(), Meta :: term(), Reason :: atom()) -> any().
Specs
on_process_registered(Scope :: atom(), Name :: term(), Pid :: pid(), Meta :: term(), Reason :: atom()) -> any().
Specs
on_process_unregistered(Scope :: atom(), Name :: term(), Pid :: pid(), Meta :: term(), Reason :: atom()) -> any().
Specs
on_registry_process_updated(Scope :: atom(), Name :: term(), Pid :: pid(), Meta :: term(), Reason :: atom()) -> any().
Specs
on_registry_process_updated(Scope :: atom(), Name :: term(), Pid :: pid(), PreviousMeta :: term(), Meta :: term(), Reason :: atom()) -> any().
Specs
resolve_registry_conflict(Scope :: atom(), Name :: term(), {Pid1 :: pid(), Meta1 :: term(), Time1 :: non_neg_integer()}, {Pid2 :: pid(), Meta2 :: term(), Time2 :: non_neg_integer()}) -> PidToKeep :: pid().