Skip to content

Add a focused CircuitBreaker module #9058

Open

Description

@Tarekkharsa

What is the problem this feature would solve?

When a dependency is down, every caller keeps sending it requests that fail slowly. Each one waits out a timeout and retries, which adds load to a service that is trying to recover. A circuit breaker stops that: after enough failures it rejects calls at once for a cooldown, then lets one trial call through to see whether the dependency is back.

Effect 4 has the building blocks (Clock, Schedule, retry, RateLimiter) but no breaker, so applications write their own. The earlier port (#2854) was closed with an invitation to open "a fresh focused PR against the current branch". This issue is to agree on the shape before I write one.

What is the feature you are proposing to solve the problem?

A small CircuitBreaker module. A sketch:

const breaker = yield* CircuitBreaker.make({
  // failures that open the circuit
  threshold: 5,
  // how long it stays open before one trial call
  cooldown: "10 seconds",
  // optional: which outcomes count, from the exit
  classify: (exit) => /* "failure" | "success" | "ignored" */
})

// rejects with CircuitBreakerOpenError while open
breaker.guard(effect)

// current state, for health checks and metrics
breaker.state

The proposed behaviour:

  • Three states. Closed, open and half-open. Only one trial call runs while half-open; concurrent callers are rejected until it settles.
  • Three outcomes, not two. A call counts as a failure, a success, or neither. Interruption is always "neither", so a cancelled request never trips or resets the breaker. A caller error such as a 401 or a 404 can also be "neither", because it says nothing about the dependency's health.
  • Time from Clock. That makes it testable with TestClock.
  • Typed rejection. The open state fails with a tagged error, and that error never counts as a failure of the breaker itself.
  • An HttpClient combinator (for example HttpClient.withCircuitBreaker), with a default classifier where server errors, timeouts and transport errors are failures, and 4xx responses are neither.

What alternatives have you considered?

  • Retry plus timeout alone. This still sends every call to a dependency that is down, so it adds load during the outage instead of shedding it.
  • RateLimiter. It caps throughput, but it doesn't react to failures.
  • Porting Rezilience as Port CircuitBreaker from Rezilience #2854 did. That is broader: bulkhead, rate limiter, sliding-window statistics. I'd keep the first version to the consecutive-failure breaker above, and leave windows and persisted shared state for later if there is demand.

Questions for maintainers

  1. Where should it live? The core effect package, or a module next to RateLimiter under effect/persistence?
  2. Should the first version include the HttpClient combinator, or ship the breaker alone?
  3. Does the failure / success / ignored classification fit Effect's conventions, or would you prefer a predicate plus a separate ignore option?

Activity

  1. GarvSaxena commented on Oct 16, 2026

    @GarvSaxena

    Hi @Tarekkharsa, I'd like to contribute to this. I noticed the earlier CircuitBreaker PR was closed, and this proposal intentionally narrows the scope for Effect 4.

    Before starting, has there been any maintainer feedback on where the module should live and whether the initial implementation should include the HttpClient combinator? I'd like to align with the intended API and avoid duplicating ongoing work.

  2. henningpokriefke commented on Oct 17, 2026

    @henningpokriefke
    Contributor

    I've had a version for this laying around locally and made a draft PR out of it. please check it out, leave feedback about everything. its currently a wip proposal from my pov. @Tarekkharsa @GarvSaxena

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions