Skip to main content

Overview

The bulkhead middleware implements the bulkhead pattern, limiting concurrent requests to prevent cascade failures and ensure fair resource allocation. Use it when you need:
  • Failure isolation between services
  • Concurrent request limiting
  • Resource protection

Installation

import "github.com/go-mizu/mizu/middlewares/bulkhead"

Quick Start

app := mizu.New()

// Limit to 10 concurrent requests
app.Use(bulkhead.New(10))

Configuration

Options

OptionTypeDefaultDescription
MaxConcurrentintRequiredMax concurrent requests
MaxWaitingint0Max waiting requests (0 = reject)
Timeouttime.Duration0Wait timeout
ErrorHandlerfunc(*mizu.Ctx) error-Custom rejection handler

Examples

Simple Limit

// Reject if more than 10 concurrent
app.Use(bulkhead.New(10))

With Waiting Queue

// Allow 10 concurrent, 20 waiting
app.Use(bulkhead.WithOptions(bulkhead.Options{
    MaxConcurrent: 10,
    MaxWaiting:    20,
}))

With Timeout

// Wait up to 5 seconds for slot
app.Use(bulkhead.WithOptions(bulkhead.Options{
    MaxConcurrent: 10,
    MaxWaiting:    50,
    Timeout:       5 * time.Second,
}))

Per-Route Limits

// Different limits for different routes
api := app.Group("/api")
api.Use(bulkhead.New(100))

heavy := app.Group("/heavy")
heavy.Use(bulkhead.New(5))

Custom Error

app.Use(bulkhead.WithOptions(bulkhead.Options{
    MaxConcurrent: 10,
    ErrorHandler: func(c *mizu.Ctx) error {
        return c.JSON(503, map[string]string{
            "error": "Service temporarily unavailable",
        })
    },
}))

API Reference

Functions

// New creates bulkhead with max concurrent
func New(maxConcurrent int) mizu.Middleware

// WithOptions creates bulkhead with configuration
func WithOptions(opts Options) mizu.Middleware

How It Works

  1. Request arrives
  2. Check if under concurrent limit
  3. If under: acquire slot, process, release
  4. If over: check waiting queue
  5. If queue full or timeout: reject with 503

HTTP Status Codes

CodeMeaning
503Service unavailable (bulkhead full)

Technical Details

Implementation

The bulkhead middleware uses a semaphore pattern with buffered channels to control concurrency:
  • Semaphore Channel: A buffered channel with capacity equal to MaxConcurrent acts as the semaphore for slot allocation
  • Waiting Queue: A counter tracks the number of requests waiting for a slot, capped at MaxWait
  • Non-blocking Acquire: First attempts to acquire a slot without blocking using select with default
  • Blocking Wait: If no slot is available, increments the waiting counter and blocks until a slot becomes available or context is cancelled
  • Thread Safety: Uses sync.Mutex to protect the waiting counter and ensure thread-safe operations

Core Components

Bulkhead Structure:
  • sem chan struct{}: Buffered channel for semaphore-based slot management
  • waiting int: Current number of requests in the waiting queue
  • maxWait int: Maximum allowed requests in the waiting queue
  • mu sync.Mutex: Mutex for protecting shared state
Manager:
  • Manages multiple named bulkheads for isolation between different services or paths
  • Thread-safe bulkhead creation and retrieval using sync.RWMutex
  • Provides aggregated statistics across all managed bulkheads
Statistics:
  • Real-time metrics including active requests, waiting requests, and available slots
  • Useful for monitoring and debugging bulkhead behavior

Request Flow

  1. Request arrives at middleware
  2. Attempts non-blocking slot acquisition via select statement
  3. If slot acquired: processes request and releases slot via defer
  4. If no slot available:
    • Checks if waiting queue is full
    • If full: rejects immediately with error handler or 503 status
    • If space available: increments waiting counter and blocks on semaphore channel
  5. When slot becomes available or context cancelled: decrements waiting counter
  6. On context cancellation: returns context error (e.g., timeout, cancellation)

Best Practices

  • Set limits based on resource capacity
  • Use different bulkheads for different services
  • Monitor rejection rates
  • Combine with circuit breaker for full resilience

Testing

Test Coverage

Test CaseDescriptionExpected Behavior
TestNewTests basic bulkhead functionality with concurrent requestsSome requests succeed (200), others are rejected (503) when bulkhead is full
TestBulkhead_StatsValidates bulkhead statistics reportingReturns correct name, max active count, and available slots
TestNewBulkhead_ErrorHandlerTests custom error handler for rejected requestsRejected requests use custom error handler with 429 status instead of default 503
TestManagerTests bulkhead manager’s get-or-create behaviorReturns same bulkhead instance for same name, different instances for different names
TestManager_StatsValidates manager’s aggregated statisticsReturns stats for all managed bulkheads by name
TestForPathTests path-based bulkhead isolationCreates separate bulkheads for different URL paths
TestDefaultsValidates default configuration valuesUses default values of 10 for MaxConcurrent and MaxWait when not specified