Skip to content

Circuit Breaker

Arnel Robles edited this page Sep 29, 2026 · 1 revision

Circuit breaker (Cushion)

Package: Carom.Extensions.

using Carom.Extensions;

var cushion = Cushion.ForService("payment-api")
    .OpenAfter(failures: 5, trackingLast: 10)
    .WithinLast(TimeSpan.FromMinutes(1))
    .When(ex => ex is HttpRequestException)
    .HalfOpenAfter(TimeSpan.FromSeconds(30));

var receipt = await CaromCushionExtensions.ShotAsync(() => payments.ChargeAsync(order), cushion);

HalfOpenAfter ends the builder and returns the Cushion.

Defaults and rules

Setting Default Rule
OpenAfter(failures, trackingLast) 5 of the last 10 failures at least 1, trackingLast at least failures
WithinLast 1 minute Must be positive. Older outcomes stop counting
HalfOpenAfter 30 seconds Must be positive
When every exception except OperationCanceledException Decides what counts as the dependency's fault

Without When, a bug in your own calling code, such as an ArgumentNullException, counts as a failure and can open the circuit on a healthy service.

States

  • Closed: calls go through and outcomes are recorded.
  • Open: calls fail at once with CircuitOpenException, which carries ServiceKey.
  • HalfOpen: after HalfOpenAfter, one caller is let through as a test. Success closes the circuit, failure opens it again. Other callers still get CircuitOpenException while the test runs. If the test fails with an exception When does not count, the result is inconclusive: the circuit goes back to Open and the delay restarts.

Cushion.GetState("payment-api") returns the current state, or null if no call has used that key yet. It creates nothing. The AspNetCore health check is built on it.

Retries run inside the breaker

CaromCushionExtensions.ShotAsync wraps the whole retry sequence in the breaker. One logical call records one outcome however many attempts it took. The default retry predicate skips CircuitOpenException, so an open circuit is not retried.

State is shared by key

State lives in a process-wide store keyed by service name. Two Cushion values with the same key share one circuit. Registering the same key with different settings throws InvalidOperationException that names the fields that differ. The store holds up to 1000 keys and evicts the least recently used when it grows past that.

Clone this wiki locally