/ex_break

A circuit breaker implementation for Elixir

Primary LanguageElixirMIT LicenseMIT

ExBreak

ExBreak is a circuit breaker implementation for Elixir.

When making function calls (typically to an external service) that may fail, you may find that you want to stop making those calls for a period of time after an error threshold is hit. This package provides a way to do that.

defmodule GitHub do
  @moduledoc """
  Makes calls to the GitHub API
  """

  @base_url "https://api.github.com"

  @doc """
  Make a GET request to the GitHub API.

  This request is wrapped in a circuit breaker—after 10 calls that return an
  error tuple (`{:error, any}`), the circuit breaker will trip, and the
  function will immediately return `{:error, :circuit_breaker_tripped}` for
  the next two minutes (120 seconds).
  """
  def get(path, token: token) do
    ExBreak.call(&do_get/2, [path, token], threshold: 10, timeout_sec: 120)
  end

  defp do_get(path, token) do
    HTTPoison.get("#{@base_url}#{path}", authorization: "Bearer #{token}")
  end
end

It is also possible to have more fine-grained control over when a circuit breaker's trip counter is incremented. For example, to ensure that only RuntimeErrors increment the counter, but not other exceptions, the match_exception option can be used.

The match_exception option is a function that will be called with the exception. If it returns true, the trip counter will be incremented when an exception occurs. Otherwise, it will not.

ExBreak.call(
  &do_get/2,
  [path, token],
  match_exception: fn
    %RuntimeError{} -> true
    _ -> false
  end
)

Likewise, match_return can be used to designate what return values increment the trip counter. This option is a function that is called with the return value of the function passed to call/3. If it returns true, the trip counter will be incremented. Otherwise, it will not.

ExBreak.call(
  &do_get/2,
  [path, token],
  match_return: fn
    {:error, :service_unavailable} -> true
    _ -> false
  end
)

If you need to do some sort of alerting when a breaker trips, you can use the on_trip option, which is a function that will receive the tripped breaker as its argument. This function is called via spawn_link/1 in the breaker's process.

ExBreak.call(
  &do_get/2,
  [path, token],
  on_trip: fn breaker ->
    Logger.error("Breaker tripped after #{breaker.break_count} breaks")
  end
)

Installation

If available in Hex, the package can be installed by adding ex_break to your list of dependencies in mix.exs:

def deps do
  [
    {:ex_break, "~> 0.1.0"}
  ]
end

Architecture

When a call ExBreak.call/3 happens, the ExBreak module asks the ExBreak.Registry to get an ExBreak.Breaker state agent for the function passed to call/3. If an agent already exists, the registry returns it. Otherwise, it starts a new one using ExBreak.BreakerSupervisor and monitors it.

If the breaker has already been tripped and the tripped state has not expired, the function passed to call/3 is never called and the value {:error, :circuit_breaker_tripped} is returned, instead.

If, however, the breaker has not been tripped (or its tripped state has expired), the function passed to call/3 is called. If an exception is raised when calling the function, or if the return value of the function matches the pattern {:error, _}, a counter in the breaker's internal state is incremented, and either the exception is re-raised or the return value is returned.

If the counter in the breaker's internal state meets a configured threshold, the breaker is marked as "tripped", and subsequent calls to call/3 with the same function will return {:error, :circuit_breaker_tripped} immediately until the tripped state expires once again.

This module is an Application which will start when you include ex_break in your application's dependencies. Its only responsibility is to start the ExBreak.Supervisor.

This is a Supervisor which starts ExBreak.DynamicSupervisor and ExBreak.Registry.

This module is a DynamicSupervisor that dynamically supervises ExBreak.Breaker agents on demand as they're needed.

This module is a registry of ExBreak.Breaker agents. When a call to ExBreak.call/3 happens, the registry finds or creates the ExBreak.Breaker registered for the given function call and returns it to the ExBreak module for use.

When an ExBreak.Breaker process exits, it is de-registered.

This module is an Agent which stores internal state about an individual circuit breaker.

Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/ex_break.