Tells notifiers what the patterns are doing.
A notifier is a module that implements this behaviour, listed in the
:notifiers option of Rexsilient:
defmodule MyApp.Notifier do
@behaviour Rexsilient.Notifier
require Logger
@impl true
def notify(:after, :retry, %{result: :failure} = state) do
Logger.warning("retry #{inspect(state.name)}: attempt #{state.attempt} failed")
end
def notify(_moment, _kind, _state), do: :ok
end
children = [
{Rexsilient, modules: [MyApp.Resilience], notifiers: [MyApp.Notifier]}
]Every notifier gets every notification; match on kind and the :name in
state to keep only the ones needed. Without notifiers, nothing is sent.
Moments
kind is the pattern, and state a map that always has its :name.
Every :before also has :monotonic_time and :system_time, and every
:after has :monotonic_time and the :duration since its :before, all
in native time units, measured in the calling process.
:circuit_breaker, around calls made with Rexsilient.CircuitBreaker.run/3:
:before- an allowed call is about to run.:after- it finished, with its:resultand:outcome.:rejected- the breaker rejected the call, with its:system_time.
Calls made with Rexsilient.CircuitBreaker.acquire/1 and
Rexsilient.CircuitBreaker.release/4 directly send none of these.
:circuit_breaker, when the breaker changes state, with the state it was
in as :from, and the :system_time at which the change happened:
:open- it opened, with the:timeoutbefore it becomes half-open, and the:outcomethat opened it. When forced withRexsilient.CircuitBreaker.force_open/1,:timeoutis:infinityand:outcomeisnil.:half_open- it lets the next call through as a probe.:probing- a probe started, with the:timeoutbefore another one is let through.:closed- a probe succeeded, it was forced closed, or the breaker manager restarted while it was not closed.
A change made with Rexsilient.CircuitBreaker.force_open/1 or
Rexsilient.CircuitBreaker.force_close/1 also has forced: true, and
one made by a manager restart has restart: true.
:retry:
:before- an attempt is about to run, with its:attemptnumber.:after- it finished, with its:attempt,:resultand:outcome.:delay- it failed and the next attempt waits:delaymilliseconds, with its:attemptand:outcome.:stop- it failed and no attempt follows, with its:attempt,:outcome, the:durationof the whole run, and the:reason::attemptswhen none remain,:strategywhen the strategy stopped,:circuit_openwhen the attempt returned{:error, :circuit_open}.
:fallback, only when the fallback function is called:
:before- it is about to run, with the:outcomeit replaces.:after- it returned, with its:outcome.
:result is :success or :failure, as classified, and :outcome is a
Rexsilient.Classify.outcome/0. More moments and keys may be added, so
end with a catch-all clause.
State changes are notified from both the calling processes and the breaker
manager, so they may arrive out of order; use :system_time to order them.
Process
Notifications are sent to a single notifier process, started by
Rexsilient. The call being notified does not wait for it, so :before
is handled after the call has started.
For each notification, every notifier is called in its own task, and the next notification waits for all of them, so each notifier sees them in the order the notifier process received them. A notifier that raises or exits only crashes its own task, which logs the error; one that takes longer than 5 seconds is killed, and the timeout is logged. Either way it is skipped for that notification, and the others are not affected.
The notifier mailbox has no limit
Notifications wait in the notifier process's mailbox, which has no limit. When they arrive faster than the notifiers handle them, for example with a notifier that takes a few milliseconds under high traffic, they arrive later and later, and the mailbox, and so memory, keeps growing.
Rexsilient.Notifier.TelemetryNotifier, the default, is the exception: it
is called in the calling process, like any :telemetry event, so its
handlers see the caller's context. Notifications sent by the breaker
manager still go through the notifier process, so the manager never runs
handlers.
Summary
Functions
Returns a specification to start this module under a supervisor.
Types
@type kind() :: :circuit_breaker | :fallback | :retry
The pattern that sends it.
@type moment() ::
:before
| :after
| :rejected
| :open
| :half_open
| :probing
| :closed
| :delay
| :stop
When the notification is sent; see Moments.
Callbacks
Functions
Returns a specification to start this module under a supervisor.
See Supervisor.