Packages

Resilience and concurrency toolkit for Elixir — bulkhead, circuit breaker, retry, hedged requests, single-flight, task combinators, and weighted semaphore.

Current section

Files

Jump to
resiliency lib resiliency.ex
Raw

lib/resiliency.ex

defmodule Resiliency do
@moduledoc """
Resilience and concurrency toolkit for Elixir.
This library bundles six complementary modules that help your application
handle failures, bound concurrency, and reduce tail latency — all with
zero runtime dependencies.
## Modules
* `Resiliency.CircuitBreaker` — Circuit breaker with sliding window failure-rate tracking and automatic recovery.
* `Resiliency.BackoffRetry` — Functional retry with composable, stream-based backoff strategies.
* `Resiliency.Hedged` — Hedged requests: fire a backup after a delay, take whichever finishes first.
* `Resiliency.SingleFlight` — Deduplicate concurrent function calls by key.
* `Resiliency.Race` — Run functions concurrently, return the first success, cancel the rest.
* `Resiliency.AllSettled` — Run functions concurrently, collect every result regardless of failures.
* `Resiliency.Map` — Map over an enumerable with bounded concurrency, cancel on first error.
* `Resiliency.FirstOk` — Try functions sequentially, return the first success.
* `Resiliency.WeightedSemaphore` — Weighted semaphore for bounding concurrent access to a shared resource.
## Common API patterns
Most modules in this library follow a consistent workflow:
1. **Start** — Add the module to your supervision tree (where applicable).
`Resiliency.CircuitBreaker`, `Resiliency.Hedged` (adaptive mode),
`Resiliency.SingleFlight`, and `Resiliency.WeightedSemaphore` require a
running process. `BackoffRetry`, `Race`, `AllSettled`, `Map`, `FirstOk`,
and stateless `Hedged.run/2` are purely functional.
2. **Call** — Wrap your operation in a zero-arity function and pass it to the
module's primary entry point:
# Retry
Resiliency.BackoffRetry.retry(fn -> fetch(url) end, max_attempts: 5)
# Hedged (stateless)
Resiliency.Hedged.run(fn -> fetch(url) end, delay: 100)
# Hedged (adaptive)
Resiliency.Hedged.run(MyHedge, fn -> fetch(url) end)
# SingleFlight
Resiliency.SingleFlight.flight(MyFlights, "user:123", fn -> load_user(123) end)
# Race
Resiliency.Race.run([fn -> svc_a() end, fn -> svc_b() end])
# WeightedSemaphore
Resiliency.WeightedSemaphore.acquire(MySem, 3, fn -> bulk_insert(rows) end)
# CircuitBreaker
Resiliency.CircuitBreaker.call(MyBreaker, fn -> HttpClient.get(url) end)
3. **Handle the result** — Every module returns `{:ok, value}` on success and
`{:error, reason}` on failure, so pattern matching is uniform across the
entire toolkit.
Combine modules for layered resilience — for example, wrap a hedged call
inside a retry, or protect a retried call behind a weighted semaphore to
avoid overwhelming a downstream service.
"""
end