circuitbreaker

package
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 19, 2026 License: Apache-2.0 Imports: 6 Imported by: 0

Documentation

Overview

Package circuitbreaker provides circuit breaker middleware for celeris.

The circuit breaker monitors downstream error rates using a sliding window and automatically stops sending requests when failure thresholds are exceeded, giving the failing service time to recover.

Three-State Machine

The breaker operates in three states:

  • Closed: All requests pass through. The sliding window tracks successes and failures. When the failure ratio meets or exceeds Config.Threshold (and at least Config.MinRequests have been observed), the breaker trips to Open.

  • Open: All requests are immediately rejected with 503 Service Unavailable. A Retry-After header is set with the remaining cooldown seconds. After Config.CooldownPeriod elapses, the breaker transitions to HalfOpen.

  • HalfOpen: Up to Config.HalfOpenMax probe requests are allowed through. If a probe succeeds, the breaker returns to Closed. If a probe fails, the breaker returns to Open. Excess requests beyond HalfOpenMax are rejected with 503.

Sliding Window Algorithm

The observation window is divided into 10 time buckets spanning Config.WindowSize. Each bucket tracks successes and failures using atomic counters. Expired buckets (older than WindowSize) are excluded from the error rate calculation. This provides a smooth, time-decaying view of the failure rate without requiring a global lock on the hot path.

Response Classification

By default, responses with status >= 500 are counted as failures. Use Config.IsError to customize classification (e.g., treat 429 as a failure, or ignore certain 5xx codes).

Retry-After Header

When the breaker is open, the middleware sets a Retry-After header with the number of seconds until the cooldown period expires. Clients that respect this header avoid unnecessary retries during the cooldown.

Programmatic Access

Use NewWithBreaker to obtain a reference to the underlying Breaker struct. This allows programmatic state inspection (Breaker.State), window counter export (Breaker.Counts) for dashboards and Prometheus, and forced reset (Breaker.Reset) for health checks, admin endpoints, or integration tests.

Middleware Ordering

Place the circuit breaker after rate limiting and before timeout middleware:

server.Use(ratelimit.New())
server.Use(circuitbreaker.New())
server.Use(timeout.New())

This ensures rate-limited requests are rejected before reaching the breaker, and timed-out requests are properly classified by the breaker.

In-Flight Requests During Transition

Requests that are already executing when the breaker transitions to Open continue to completion — only NEW requests are rejected. This is by design: interrupting in-flight requests could cause data corruption or incomplete operations.

Per-Endpoint Breakers

To use separate breakers for different services or route groups, create multiple instances and apply them to the appropriate groups:

payments := s.Group("/api/payments")
payments.Use(circuitbreaker.New(circuitbreaker.Config{Threshold: 0.3}))

Thread Safety

The breaker is safe for concurrent use. State reads and counter updates use atomic operations (lock-free hot path). State transitions acquire a mutex with double-check locking to prevent duplicate transitions.

Basic usage with defaults (50% threshold, minimum 10 requests, 10s window):

server.Use(circuitbreaker.New())

Custom threshold and cooldown:

server.Use(circuitbreaker.New(circuitbreaker.Config{
    Threshold:      0.3,
    MinRequests:    20,
    CooldownPeriod: time.Minute,
}))

Programmatic access for health checks:

mw, breaker := circuitbreaker.NewWithBreaker()
server.Use(mw)
server.GET("/health", func(c *celeris.Context) error {
    if breaker.State() == circuitbreaker.Open {
        return c.JSON(503, map[string]string{"circuit": "open"})
    }
    return c.JSON(200, map[string]string{"circuit": "closed"})
})

ErrServiceUnavailable is the exported sentinel error (503) returned when the breaker is open, usable with errors.Is for error handling in upstream middleware.

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrServiceUnavailable = celeris.ErrServiceUnavailable

ErrServiceUnavailable aliases celeris.ErrServiceUnavailable so callers can match across timeout/circuitbreaker/ratelimit with errors.Is.

Functions

func New

func New(config ...Config) celeris.HandlerFunc

New creates a circuit breaker middleware with the given config.

Example
package main

import (
	"github.com/goceleris/celeris/middleware/circuitbreaker"
)

func main() {
	// Zero-config: 50% threshold, 10 min requests, 10s window, 30s cooldown.
	_ = circuitbreaker.New()
}
Example (Custom)
package main

import (
	"time"

	"github.com/goceleris/celeris/middleware/circuitbreaker"
)

func main() {
	// Custom threshold and cooldown period.
	_ = circuitbreaker.New(circuitbreaker.Config{
		Threshold:      0.3,
		MinRequests:    20,
		CooldownPeriod: time.Minute,
	})
}
Example (PerGroup)
package main

import (
	"github.com/goceleris/celeris/middleware/circuitbreaker"
)

func main() {
	// Separate circuit breakers for different upstream services.
	// s := celeris.New()
	// payments := s.Group("/api/payments")
	// payments.Use(circuitbreaker.New(circuitbreaker.Config{Threshold: 0.3}))
	// internal := s.Group("/internal")
	// internal.Use(circuitbreaker.New(circuitbreaker.Config{Threshold: 0.7}))
	_ = circuitbreaker.New(circuitbreaker.Config{Threshold: 0.3})
}

Types

type Breaker

type Breaker struct {
	// contains filtered or unexported fields
}

Breaker holds the circuit breaker state. Use NewWithBreaker to obtain a reference for programmatic state inspection and reset.

func NewWithBreaker

func NewWithBreaker(config ...Config) (celeris.HandlerFunc, *Breaker)

NewWithBreaker creates a circuit breaker middleware and returns both the handler and the underlying Breaker for programmatic access.

Example
package main

import (
	"fmt"

	"github.com/goceleris/celeris/middleware/circuitbreaker"
)

func main() {
	// Obtain a Breaker reference for programmatic state inspection.
	mw, breaker := circuitbreaker.NewWithBreaker(circuitbreaker.Config{
		Threshold:   0.5,
		MinRequests: 10,
		OnStateChange: func(from, to circuitbreaker.State) {
			fmt.Printf("circuit breaker: %s -> %s\n", from, to)
		},
	})
	_ = mw

	// Use the breaker for health checks or admin endpoints.
	fmt.Println(breaker.State())

}
Output:
closed

func (*Breaker) Counts

func (b *Breaker) Counts() (total, failures int64)

Counts returns the current sliding window totals.

func (*Breaker) Reset

func (b *Breaker) Reset()

Reset forces the breaker back to Closed and clears the observation window.

func (*Breaker) State

func (b *Breaker) State() State

State returns the current circuit breaker state.

type Config

type Config struct {
	// Skip defines a function to skip this middleware for certain requests.
	Skip func(c *celeris.Context) bool

	// SkipPaths lists paths to skip (exact match).
	SkipPaths []string

	// Threshold is the failure ratio (failures/total) that trips the breaker.
	// Must be in (0, 1] — greater than 0 and at most 1. Default: 0.5.
	Threshold float64

	// MinRequests is the minimum number of requests in the current window
	// before the breaker can trip. Prevents premature tripping on small
	// sample sizes. Default: 10.
	MinRequests int

	// WindowSize is the duration of the sliding observation window.
	// Default: 10s.
	WindowSize time.Duration

	// CooldownPeriod is how long the breaker stays open before transitioning
	// to half-open. Default: 30s.
	CooldownPeriod time.Duration

	// HalfOpenMax is the maximum number of probe requests allowed through
	// in the half-open state. Default: 1.
	HalfOpenMax int

	// IsError determines whether a response should be counted as a failure.
	// Receives the error returned by the handler and the response status code.
	// Default: status >= 500.
	IsError func(err error, statusCode int) bool

	// OnStateChange is called when the breaker transitions between states.
	// Called under a mutex; keep the callback fast.
	OnStateChange func(from, to State)

	// ErrorHandler is called to produce a response when the breaker is open.
	// Default: returns ErrServiceUnavailable (503).
	ErrorHandler func(c *celeris.Context, err error) error
}

Config defines the circuit breaker middleware configuration.

type State

type State int

State represents the circuit breaker state.

const (
	// Closed allows all requests through and monitors the error rate.
	Closed State = iota
	// Open rejects all requests immediately with 503.
	Open
	// HalfOpen allows a limited number of probe requests through.
	HalfOpen
)

func (State) String

func (s State) String() string

String returns the human-readable state name.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL