Class: DilisensePepClient::CircuitBreaker
- Inherits:
-
Object
- Object
- DilisensePepClient::CircuitBreaker
- Defined in:
- lib/dilisense_pep_client/circuit_breaker.rb
Overview
Circuit breaker implementation for API resilience and fault tolerance
This class implements the Circuit Breaker pattern to protect against cascading failures when the Dilisense API becomes unavailable or starts returning errors frequently. It prevents unnecessary load on a failing service by temporarily blocking requests and allowing the service time to recover.
States:
-
CLOSED: Normal operation, requests pass through
-
OPEN: Service is failing, all requests are blocked
-
HALF_OPEN: Testing if service has recovered, limited requests allowed
The circuit breaker automatically transitions between states based on:
-
Failure threshold: Number of consecutive failures before opening
-
Recovery timeout: Time to wait before attempting to recover
-
Success criteria: Requirements to close the circuit after half-open
Features:
-
Thread-safe operation using concurrent-ruby primitives
-
Configurable failure thresholds and recovery timeouts
-
Timeout protection for individual requests
-
Comprehensive metrics and logging
-
Security event logging for monitoring
Defined Under Namespace
Classes: CircuitOpenError
Constant Summary collapse
- STATES =
Valid circuit breaker states following the standard pattern
%i[closed open half_open].freeze
Instance Method Summary collapse
-
#call(&block) ⇒ Object
Execute a block of code with circuit breaker protection This is the main method that wraps your API calls or other potentially failing operations.
- #failure_count ⇒ Object
- #force_open! ⇒ Object
-
#initialize(service_name:, failure_threshold: 5, recovery_timeout: 60, timeout: 30, exceptions: [StandardError]) ⇒ CircuitBreaker
constructor
Initialize a new circuit breaker with specified configuration.
- #metrics ⇒ Object
- #reset! ⇒ Object
- #state ⇒ Object
- #success_count ⇒ Object
Constructor Details
#initialize(service_name:, failure_threshold: 5, recovery_timeout: 60, timeout: 30, exceptions: [StandardError]) ⇒ CircuitBreaker
Initialize a new circuit breaker with specified configuration
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 68 def initialize( service_name:, failure_threshold: 5, recovery_timeout: 60, timeout: 30, exceptions: [StandardError] ) @service_name = service_name @failure_threshold = failure_threshold @recovery_timeout = recovery_timeout @timeout = timeout @exceptions = exceptions # Initialize state - circuit starts in CLOSED (normal) state @state = :closed @failure_count = Concurrent::AtomicFixnum.new(0) # Thread-safe failure counter @last_failure_time = Concurrent::AtomicReference.new # Track when last failure occurred @next_attempt_time = Concurrent::AtomicReference.new # When to allow next attempt after opening @success_count = Concurrent::AtomicFixnum.new(0) # Track successful requests @mutex = Mutex.new # Synchronize state transitions end |
Instance Method Details
#call(&block) ⇒ Object
Execute a block of code with circuit breaker protection This is the main method that wraps your API calls or other potentially failing operations
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 102 def call(&block) case state when :open # Circuit is open - check if enough time has passed to allow a test request check_if_half_open_allowed raise CircuitOpenError.new(@service_name, @next_attempt_time.value) when :half_open # Circuit is testing recovery - attempt the request and reset if successful attempt_reset(&block) when :closed # Circuit is closed - normal operation, execute the request execute(&block) end rescue *@exceptions => e # Catch configured exception types and record as failures record_failure(e) raise end |
#failure_count ⇒ Object
125 126 127 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 125 def failure_count @failure_count.value end |
#force_open! ⇒ Object
158 159 160 161 162 163 164 165 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 158 def force_open! @mutex.synchronize do @state = :open @next_attempt_time.value = Time.now + @recovery_timeout end Logger.logger.warn("Circuit breaker forced open", service_name: @service_name) end |
#metrics ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 133 def metrics { service_name: @service_name, state: state, failure_count: failure_count, success_count: success_count, failure_threshold: @failure_threshold, recovery_timeout: @recovery_timeout, last_failure_time: @last_failure_time.value, next_attempt_time: @next_attempt_time.value } end |
#reset! ⇒ Object
146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 146 def reset! @mutex.synchronize do @state = :closed @failure_count.value = 0 @success_count.value = 0 @last_failure_time.value = nil @next_attempt_time.value = nil end Logger.logger.info("Circuit breaker reset", service_name: @service_name) end |
#state ⇒ Object
121 122 123 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 121 def state @mutex.synchronize { @state } end |
#success_count ⇒ Object
129 130 131 |
# File 'lib/dilisense_pep_client/circuit_breaker.rb', line 129 def success_count @success_count.value end |