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.

The argument Reason in the callbacks can be:
  • normal for expected operations.
  • Crash reasons when processes die (for on_process_unregistered/5 and on_process_left/5).
  • {syn_remote_scope_node_up, Scope, Node} for on_process_registered/5 and on_process_joined/5 when the callbacks are called due to nodes syncing.
  • {syn_remote_scope_node_down, Scope, Node} for on_process_unregistered/5 and on_process_left/5 when the callbacks are called due to nodes disconnecting.
  • syn_conflict_resolution for on_process_registered/5 and on_process_unregistered/5 during registry conflict resolution.
  • undefined for on_process_unregistered/5 and on_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 with erlang: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 the on_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

Link to this callback

on_group_process_updated/5

View Source (optional)

Specs

on_group_process_updated(Scope :: atom(),
                         GroupName :: term(),
                         Pid :: pid(),
                         Meta :: term(),
                         Reason :: atom()) ->
                            any().
Link to this callback

on_group_process_updated/6

View Source (optional)

Specs

on_group_process_updated(Scope :: atom(),
                         GroupName :: term(),
                         Pid :: pid(),
                         PreviousMeta :: term(),
                         Meta :: term(),
                         Reason :: atom()) ->
                            any().
Link to this callback

on_process_joined/5

View Source (optional)

Specs

on_process_joined(Scope :: atom(),
                  GroupName :: term(),
                  Pid :: pid(),
                  Meta :: term(),
                  Reason :: atom()) ->
                     any().
Link to this callback

on_process_left/5

View Source (optional)

Specs

on_process_left(Scope :: atom(),
                GroupName :: term(),
                Pid :: pid(),
                Meta :: term(),
                Reason :: atom()) ->
                   any().
Link to this callback

on_process_registered/5

View Source (optional)

Specs

on_process_registered(Scope :: atom(),
                      Name :: term(),
                      Pid :: pid(),
                      Meta :: term(),
                      Reason :: atom()) ->
                         any().
Link to this callback

on_process_unregistered/5

View Source (optional)

Specs

on_process_unregistered(Scope :: atom(),
                        Name :: term(),
                        Pid :: pid(),
                        Meta :: term(),
                        Reason :: atom()) ->
                           any().
Link to this callback

on_registry_process_updated/5

View Source (optional)

Specs

on_registry_process_updated(Scope :: atom(),
                            Name :: term(),
                            Pid :: pid(),
                            Meta :: term(),
                            Reason :: atom()) ->
                               any().
Link to this callback

on_registry_process_updated/6

View Source (optional)

Specs

on_registry_process_updated(Scope :: atom(),
                            Name :: term(),
                            Pid :: pid(),
                            PreviousMeta :: term(),
                            Meta :: term(),
                            Reason :: atom()) ->
                               any().
Link to this callback

resolve_registry_conflict/4

View Source (optional)

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