Class: Woods::Resilience::CircuitBreaker

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/resilience/circuit_breaker.rb

Overview

Circuit breaker pattern for protecting external service calls.

Tracks failures and transitions between three states:

  • :closed — normal operation, calls pass through

  • :open — too many failures, calls are rejected immediately

  • :half_open — testing recovery, one call is allowed through

Examples:

Basic usage

breaker = CircuitBreaker.new(threshold: 5, reset_timeout: 60)
result = breaker.call { external_service.request }

With retry logic

breaker = CircuitBreaker.new(threshold: 3, reset_timeout: 30)
begin
  breaker.call { api.embed(text) }
rescue CircuitOpenError
  # Service is down, use fallback
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(threshold: 5, reset_timeout: 60) ⇒ CircuitBreaker

Returns a new instance of CircuitBreaker.

Parameters:

  • threshold (Integer) (defaults to: 5)

    Number of consecutive failures before opening the circuit

  • reset_timeout (Numeric) (defaults to: 60)

    Seconds to wait before transitioning from open to half_open



39
40
41
42
43
44
45
46
# File 'lib/woods/resilience/circuit_breaker.rb', line 39

def initialize(threshold: 5, reset_timeout: 60)
  @threshold = threshold
  @reset_timeout = reset_timeout
  @state = :closed
  @failure_count = 0
  @last_failure_time = nil
  @mutex = Mutex.new
end

Instance Attribute Details

#stateSymbol (readonly)

Returns Current state — :closed, :open, or :half_open.

Returns:

  • (Symbol)

    Current state — :closed, :open, or :half_open



35
36
37
# File 'lib/woods/resilience/circuit_breaker.rb', line 35

def state
  @state
end

Instance Method Details

#call { ... } ⇒ Object

Execute a block through the circuit breaker.

Yields:

  • The block to execute

Returns:

  • (Object)

    The return value of the block

Raises:

  • (CircuitOpenError)

    if the circuit is open and the timeout has not elapsed

  • (StandardError)

    re-raises any error from the block



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/woods/resilience/circuit_breaker.rb', line 54

def call(&block)
  # Phase 1: Check state under mutex
  @mutex.synchronize do
    case @state
    when :open
      unless monotonic_now - @last_failure_time >= @reset_timeout
        raise CircuitOpenError, "Circuit breaker is open (#{@failure_count} failures)"
      end

      @state = :half_open
    end
  end

  # Phase 2: Execute outside mutex
  result = block.call

  # Phase 3: Record success under mutex
  @mutex.synchronize { reset! }

  result
rescue CircuitOpenError
  raise
rescue StandardError => e
  # Phase 4: Record failure under mutex
  @mutex.synchronize { record_failure }
  raise e
end