Rexsilient.CircuitBreaker.Recover behaviour (Rexsilient v1.0.0)

Copy Markdown View Source

Decides how long a breaker waits before it lets a probe call through.

timeout/2 is called by the manager with a context whose :transition is the change of state that starts a timer:

  • {:closed, :open} - the breaker tripped. Returns how long it stays open.

  • {:probing, :open} - the probe failed. Returns how long it stays open.

  • {:half_open, :probing} - a probe started. Returns how long it may run before the breaker becomes half-open again and lets another probe through.

A successful probe closes the breaker without calling timeout/2, so the next {:closed, :open} is the first call after a recovery.

init/1 receives the options given to recover and returns the state. The first timeout/2 call gets that state; it returns the timeout and the new state, which the manager passes to the next call. It is only called from the manager, so the state can be a plain value. The state is not kept across manager restarts: init/1 is called again.

The state can also hold what the transition alone does not tell. For example, a {:half_open, :probing} right after another one means the previous probe timed out.

timeout/2 runs in the manager

One manager process serves every breaker, and timeout/2 runs inside it. A slow timeout/2 delays the timers of all breakers, and one that raises crashes the manager: when it restarts, every breaker restarts closed. Take care with what it does.

Built-in: Rexsilient.CircuitBreaker.Recover.BasicTimeout.

Example

defmodule MyRecover do
  @behaviour Rexsilient.CircuitBreaker.Recover

  @impl true
  def init(_opts), do: 1_000

  @impl true
  def timeout(_context, timeout), do: {timeout, timeout * 2}
end

Each timeout/2 gets the state the previous one returned: 1000, 2000, 4000, and so on. It only starts again from init/1 when the manager restarts.

Summary

Types

What timeout/2 knows about the change of state

The value returned by init/1, then by each timeout/2.

The {from, to} change of state that starts a timer; see the module documentation for the ones passed.

Callbacks

Validates opts and returns the initial state.

Returns how long, in milliseconds, to wait after the context's :transition, and the new state.

Types

context()

@type context() :: %{
  transition: transition(),
  outcome: Rexsilient.Classify.outcome() | nil
}

What timeout/2 knows about the change of state:

  • :transition - the change of state that starts the timer.

  • :outcome - the outcome of the call that opened the breaker, for {:closed, :open} and {:probing, :open}. When a strategy opens the breaker after several calls, it is the last one. nil for {:half_open, :probing}, and when the failure was recorded with Rexsilient.CircuitBreaker.release/4 without an outcome.

More keys may be added, so match only on the ones needed.

state()

@type state() :: term()

The value returned by init/1, then by each timeout/2.

transition()

The {from, to} change of state that starts a timer; see the module documentation for the ones passed.

Callbacks

init(opts)

@callback init(opts :: keyword()) :: state()

Validates opts and returns the initial state.

Called when the circuit breaker is registered, and again when the manager restarts. Raise ArgumentError for invalid options, as Rexsilient.Options.validate!/2 does; the error is raised again with the circuit breaker's name.

timeout(context, state)

@callback timeout(context :: context(), state :: state()) ::
  {timeout :: non_neg_integer(), state :: state()}

Returns how long, in milliseconds, to wait after the context's :transition, and the new state.