ExBreak
ExBreak is a circuit breaker implementation for Elixir.
When making function calls 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 RuntimeError
s 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],
threshold: 10,
timeout_sec: 120,
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],
timeout_sec: 120,
match_return: fn
{:error, :service_unavailable} -> true
_ -> false
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.
ExBreak.Application
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
.
ExBreak.Supervisor
This is a Supervisor
which starts ExBreak.DynamicSupervisor
and ExBreak.Registry
.
ExBreak.BreakerSupervisor
This module is a DynamicSupervisor
that dynamically supervises ExBreak.Breaker
agents on demand as they're needed.
ExBreak.Registry
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.
ExBreak.Breaker
This module is an Agent
which stores internal state about an individual circuit breaker.
Architecture Diagram
╔═══════════════════════════╗
║ ║░
║ ExBreak.Application ║░
║ ║░
╚═══════════════════════════╝░
░░░░░░░░░░░░░│░░░░░░░░░░░░░░░
│
▼
╔═══════════════════════════╗
║ ║░
║ ExBreak.Supervisor ║░
║ ║░
╚═══════════════════════════╝░
░░░░░░░░░░░░░│░░░░░░░░░░░░░░░
│
┌─────────────┴────────────────────────────┐
│ │
▼ ▼
╔═══════════════════════════╗ ╔═══════════════════════════╗
║ ║░ ║ ║░
║ ExBreak.BreakerSupervisor ║░ ║ ExBreak.Registry ║░
║ ║░ ║ ║░
╚═══════════════════════════╝░ ╚═══════════════════════════╝░
░░░░░░░░░░░░░│░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
│
┌──────────┴───────────┬──────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ ExBreak.Breaker │ │ ExBreak.Breaker │ │ ExBreak.Breaker │
└───────────────────┘ └───────────────────┘ └───────────────────┘