Class: A2A::Client::Middleware::CircuitBreakerInterceptor
- Inherits:
-
Object
- Object
- A2A::Client::Middleware::CircuitBreakerInterceptor
- Defined in:
- lib/a2a/client/middleware/circuit_breaker_interceptor.rb
Constant Summary collapse
- CLOSED =
Circuit breaker states
:closed- OPEN =
:open- HALF_OPEN =
:half_open
Instance Attribute Summary collapse
-
#expected_errors ⇒ Object
readonly
Returns the value of attribute expected_errors.
-
#failure_count ⇒ Object
readonly
Returns the value of attribute failure_count.
-
#failure_threshold ⇒ Object
readonly
Returns the value of attribute failure_threshold.
-
#last_failure_time ⇒ Object
readonly
Returns the value of attribute last_failure_time.
-
#state ⇒ Object
readonly
Returns the value of attribute state.
-
#success_count ⇒ Object
readonly
Returns the value of attribute success_count.
-
#timeout ⇒ Object
readonly
Returns the value of attribute timeout.
Instance Method Summary collapse
-
#call(request, context, next_middleware) ⇒ Object
Execute request with circuit breaker logic.
-
#check_timeout_and_execute(request, context, next_middleware) ⇒ Object
private
Check timeout and execute request when circuit is open.
-
#circuit_breaker_error?(error) ⇒ Boolean
private
Check if error should trigger circuit breaker.
-
#closed? ⇒ Boolean
Check if the circuit breaker is closed.
-
#default_expected_errors ⇒ Object
private
Default error classes that should trigger circuit breaker.
-
#execute_half_open_request(request, context, next_middleware) ⇒ Object
private
Execute request in half-open state.
-
#execute_request(request, context, next_middleware) ⇒ Object
private
Execute request in closed state.
-
#half_open? ⇒ Boolean
Check if the circuit breaker is half-open.
-
#initialize(failure_threshold: 5, timeout: 60, expected_errors: nil) ⇒ CircuitBreakerInterceptor
constructor
Initialize circuit breaker interceptor.
-
#on_failure ⇒ Object
private
Handle failed request.
-
#on_half_open_failure ⇒ Object
private
Handle failed request in half-open state.
-
#on_half_open_success ⇒ Object
private
Handle successful request in half-open state.
-
#on_success ⇒ Object
private
Handle successful request.
-
#open? ⇒ Boolean
Check if the circuit breaker is open.
-
#reset! ⇒ Object
Reset the circuit breaker to closed state.
-
#status ⇒ Hash
Get current circuit breaker status.
-
#time_until_half_open ⇒ Object
private
Calculate time until circuit can be half-open.
-
#timeout_expired? ⇒ Boolean
private
Check if timeout has expired for opening circuit.
-
#trip! ⇒ Object
Force the circuit breaker to open state.
-
#validate_configuration! ⇒ Object
private
Validate configuration parameters.
Constructor Details
#initialize(failure_threshold: 5, timeout: 60, expected_errors: nil) ⇒ CircuitBreakerInterceptor
Initialize circuit breaker interceptor
27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 27 def initialize(failure_threshold: 5, timeout: 60, expected_errors: nil) @failure_threshold = failure_threshold @timeout = timeout @expected_errors = expected_errors || default_expected_errors @state = CLOSED @failure_count = 0 @success_count = 0 @last_failure_time = nil @mutex = Mutex.new validate_configuration! end |
Instance Attribute Details
#expected_errors ⇒ Object (readonly)
Returns the value of attribute expected_errors.
18 19 20 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 18 def expected_errors @expected_errors end |
#failure_count ⇒ Object (readonly)
Returns the value of attribute failure_count.
18 19 20 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 18 def failure_count @failure_count end |
#failure_threshold ⇒ Object (readonly)
Returns the value of attribute failure_threshold.
18 19 20 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 18 def failure_threshold @failure_threshold end |
#last_failure_time ⇒ Object (readonly)
Returns the value of attribute last_failure_time.
18 19 20 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 18 def last_failure_time @last_failure_time end |
#state ⇒ Object (readonly)
Returns the value of attribute state.
18 19 20 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 18 def state @state end |
#success_count ⇒ Object (readonly)
Returns the value of attribute success_count.
18 19 20 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 18 def success_count @success_count end |
#timeout ⇒ Object (readonly)
Returns the value of attribute timeout.
18 19 20 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 18 def timeout @timeout end |
Instance Method Details
#call(request, context, next_middleware) ⇒ Object
Execute request with circuit breaker logic
47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 47 def call(request, context, next_middleware) @mutex.synchronize do case @state when CLOSED execute_request(request, context, next_middleware) when OPEN check_timeout_and_execute(request, context, next_middleware) when HALF_OPEN execute_half_open_request(request, context, next_middleware) end end end |
#check_timeout_and_execute(request, context, next_middleware) ⇒ Object (private)
Check timeout and execute request when circuit is open
137 138 139 140 141 142 143 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 137 def check_timeout_and_execute(request, context, next_middleware) raise A2A::Errors::AgentUnavailable, "Circuit breaker is OPEN. Service unavailable." unless timeout_expired? @state = HALF_OPEN @success_count = 0 execute_half_open_request(request, context, next_middleware) end |
#circuit_breaker_error?(error) ⇒ Boolean (private)
Check if error should trigger circuit breaker
212 213 214 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 212 def circuit_breaker_error?(error) @expected_errors.any? { |error_class| error.is_a?(error_class) } end |
#closed? ⇒ Boolean
Check if the circuit breaker is closed
90 91 92 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 90 def closed? @state == CLOSED end |
#default_expected_errors ⇒ Object (private)
Default error classes that should trigger circuit breaker
218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 218 def default_expected_errors [ A2A::Errors::TimeoutError, A2A::Errors::HTTPError, A2A::Errors::TransportError, A2A::Errors::AgentUnavailable, A2A::Errors::ResourceExhausted, A2A::Errors::InternalError, Faraday::TimeoutError, Faraday::ConnectionFailed ] end |
#execute_half_open_request(request, context, next_middleware) ⇒ Object (private)
Execute request in half-open state
147 148 149 150 151 152 153 154 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 147 def execute_half_open_request(request, context, next_middleware) response = next_middleware.call(request, context) on_half_open_success response rescue StandardError => e on_half_open_failure if circuit_breaker_error?(e) raise e end |
#execute_request(request, context, next_middleware) ⇒ Object (private)
Execute request in closed state
126 127 128 129 130 131 132 133 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 126 def execute_request(request, context, next_middleware) response = next_middleware.call(request, context) on_success response rescue StandardError => e on_failure if circuit_breaker_error?(e) raise e end |
#half_open? ⇒ Boolean
Check if the circuit breaker is half-open
98 99 100 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 98 def half_open? @state == HALF_OPEN end |
#on_failure ⇒ Object (private)
Handle failed request
164 165 166 167 168 169 170 171 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 164 def on_failure @failure_count += 1 @last_failure_time = Time.now return unless @failure_count >= @failure_threshold @state = OPEN end |
#on_half_open_failure ⇒ Object (private)
Handle failed request in half-open state
186 187 188 189 190 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 186 def on_half_open_failure @state = OPEN @failure_count += 1 @last_failure_time = Time.now end |
#on_half_open_success ⇒ Object (private)
Handle successful request in half-open state
175 176 177 178 179 180 181 182 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 175 def on_half_open_success @success_count += 1 # Close circuit after successful request in half-open state @state = CLOSED @failure_count = 0 @success_count = 0 end |
#on_success ⇒ Object (private)
Handle successful request
158 159 160 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 158 def on_success @failure_count = 0 end |
#open? ⇒ Boolean
Check if the circuit breaker is open
82 83 84 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 82 def open? @state == OPEN end |
#reset! ⇒ Object
Reset the circuit breaker to closed state
104 105 106 107 108 109 110 111 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 104 def reset! @mutex.synchronize do @state = CLOSED @failure_count = 0 @success_count = 0 @last_failure_time = nil end end |
#status ⇒ Hash
Get current circuit breaker status
64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 64 def status @mutex.synchronize do { state: @state, failure_count: @failure_count, success_count: @success_count, failure_threshold: @failure_threshold, timeout: @timeout, last_failure_time: @last_failure_time, time_until_half_open: time_until_half_open } end end |
#time_until_half_open ⇒ Object (private)
Calculate time until circuit can be half-open
202 203 204 205 206 207 208 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 202 def time_until_half_open return 0 unless @state == OPEN && @last_failure_time elapsed = Time.now - @last_failure_time remaining = @timeout - elapsed [remaining, 0].max end |
#timeout_expired? ⇒ Boolean (private)
Check if timeout has expired for opening circuit
194 195 196 197 198 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 194 def timeout_expired? return false unless @last_failure_time Time.now - @last_failure_time >= @timeout end |
#trip! ⇒ Object
Force the circuit breaker to open state
115 116 117 118 119 120 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 115 def trip! @mutex.synchronize do @state = OPEN @last_failure_time = Time.now end end |
#validate_configuration! ⇒ Object (private)
Validate configuration parameters
233 234 235 236 237 238 239 240 241 |
# File 'lib/a2a/client/middleware/circuit_breaker_interceptor.rb', line 233 def validate_configuration! raise ArgumentError, "failure_threshold must be positive" if @failure_threshold <= 0 raise ArgumentError, "timeout must be positive" if @timeout <= 0 return if @expected_errors.is_a?(Array) raise ArgumentError, "expected_errors must be an array" end |