lucifer07 / fuse
Circuit breaker for Laravel queue jobs. PHP 8.1 compatible.
Requires
- php: ^8.1
- illuminate/cache: ^10.0
- illuminate/contracts: ^10.0
- illuminate/queue: ^10.0
- illuminate/support: ^10.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.0
- orchestra/testbench: ^8.0
- pestphp/pest: ^2.0
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-03-04 10:23:11 UTC
README
Circuit breaker pattern implementation for Laravel queue jobs. PHP 8.1+ compatible.
Features
- Three-State Circuit Breaker — CLOSED (normal), OPEN (protected), HALF-OPEN (testing recovery)
- Intelligent Failure Classification — Rate limits (429) and auth errors (401, 403) don't trip the circuit
- Peak Hours Support — Different thresholds for business hours vs. off-peak
- Fixed Window Tracking — Minute-based buckets with automatic expiration
- Thundering Herd Prevention —
Cache::lock()ensures only one worker probes during recovery - Zero Data Loss — Jobs are delayed with
release(), not failed permanently - Automatic Recovery — Circuit tests and heals itself when services return
- Per-Service Circuits — Separate breakers for each service
- Laravel Events — Get notified on state transitions for alerting and monitoring
Requirements
- PHP 8.1+
- Laravel 10+
- Redis (recommended) or any Laravel cache driver
Installation
composer require lucifer07/fuse
Publish the configuration:
php artisan vendor:publish --tag=fuse-config
Quick Start
Add the middleware to your job:
<?php namespace App\Jobs; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Fuse\Middleware\CircuitBreakerMiddleware; class ChargeCustomer implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public $tries = 0; // Unlimited releases public $maxExceptions = 3; // Only real failures count public function middleware(): array { return [new CircuitBreakerMiddleware('stripe')]; } public function handle(): void { // Your payment logic - unchanged Stripe::charges()->create([...]); } }
How It Works
CLOSED (Normal Operations)
All requests pass through. Failures are tracked in the background.
OPEN (Protection Mode)
After failure threshold is exceeded, circuit trips. Jobs fail instantly (1ms, not 30s) and are delayed for automatic retry. No API calls are made.
HALF-OPEN (Testing Recovery)
After timeout period, one probe request tests if service recovered. Success closes circuit. Failure reopens it.
Configuration
Edit config/fuse.php:
return [ 'enabled' => env('FUSE_ENABLED', true), 'default_threshold' => 50, // Failure rate percentage to trip circuit 'default_timeout' => 60, // Seconds before testing recovery 'default_min_requests' => 10, // Minimum requests before evaluating 'services' => [ 'stripe' => [ 'threshold' => 40, // Off-peak threshold 'peak_hours_threshold' => 60, // Peak hours threshold 'peak_hours_start' => 9, // 9 AM 'peak_hours_end' => 17, // 5 PM 'timeout' => 30, 'min_requests' => 5, ], 'mailgun' => [ 'threshold' => 50, 'peak_hours_threshold' => 70, 'peak_hours_start' => 9, 'peak_hours_end' => 17, 'timeout' => 120, 'min_requests' => 10, ], ], ];
Peak Hours
Configure different thresholds for business hours when every transaction matters:
'stripe' => [ 'threshold' => 40, // Off-peak: more sensitive (40%) 'peak_hours_threshold' => 60, // Peak hours: more tolerant (60%) 'peak_hours_start' => 9, // 9 AM 'peak_hours_end' => 17, // 5 PM 'timeout' => 30, 'min_requests' => 5, ],
During peak hours (9 AM - 5 PM), the circuit uses the higher threshold to maximize successful transactions. Outside peak hours, it uses the lower threshold for earlier protection.
This is especially useful for payment processing, email sending, and other critical services where:
- Peak hours (e.g., 9-17): Higher threshold to avoid blocking transactions when volume is high
- Off-peak (e.g., 17-9): Lower threshold for faster detection of issues during low-traffic periods
Intelligent Failure Classification
Not all errors indicate service is down. Fuse only counts real outages:
| Error Type | Counted as Failure? | Reason |
|---|---|---|
| 500, 502, 503 | Yes | Server errors indicate service problems |
| Connection timeout | Yes | Service is unreachable |
| Connection refused | Yes | Service is unreachable |
| 429 Too Many Requests | No | Service is healthy, just rate limiting |
| 401 Unauthorized | No | Your API key is wrong, not a service issue |
| 403 Forbidden | No | Permission issue, not a service outage |
| 400 Bad Request | Yes | Could indicate API issues |
| 404 Not Found | Yes | Could indicate API changes |
Events
Fuse dispatches Laravel events on state transitions:
use Fuse\Events\CircuitBreakerOpened; use Fuse\Events\CircuitBreakerHalfOpen; use Fuse\Events\CircuitBreakerClosed; // In EventServiceProvider protected $listen = [ CircuitBreakerOpened::class => [ AlertOnCircuitOpen::class, ], CircuitBreakerClosed::class => [ LogCircuitRecovery::class, ], ];
Event Properties
CircuitBreakerOpened:
$service— The service name (e.g., "stripe")$failureRate— Current failure percentage$attempts— Total requests in the window$failures— Failed requests in the window
CircuitBreakerHalfOpen:
$service— The service name
CircuitBreakerClosed:
$service— The service name
Direct Usage
Use the circuit breaker directly outside jobs:
use Fuse\CircuitBreaker; $breaker = new CircuitBreaker('stripe'); if (!$breaker->isOpen()) { try { $result = Stripe::charges()->create([...]); $breaker->recordSuccess(); return $result; } catch (Exception $e) { $breaker->recordFailure($e); throw $e; } } else { // Circuit is open - use fallback return $this->fallbackResponse(); }
Check Circuit State
$breaker = new CircuitBreaker('stripe'); $breaker->isClosed(); // Normal operations $breaker->isOpen(); // Protected, failing fast $breaker->isHalfOpen(); // Testing recovery $breaker->getStats(); // Get full statistics $breaker->reset(); // Manually reset to closed
Stats Output
$stats = $breaker->getStats(); [ 'state' => 'open', 'attempts' => 15, 'failures' => 12, 'failure_rate' => 80.0, 'opened_at' => 1706922000, 'recovery_at' => 1706922060, 'timeout' => 60, 'threshold' => 50, 'min_requests' => 10, ]
Dynamic Configuration
Enable/disable circuit breaker at runtime:
use Illuminate\Support\Facades\Cache; // Disable circuit breaker Cache::put('fuse:enabled', false); // Enable circuit breaker Cache::put('fuse:enabled', true); // Let config decide Cache::forget('fuse:enabled');
License
MIT