Class: HTM::CircuitBreaker

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

Overview

Circuit Breaker - Prevents cascading failures from external LLM services

Implements the circuit breaker pattern to protect against repeated failures when calling external LLM APIs for embeddings or tag extraction.

States:

  • :closed - Normal operation, requests flow through

  • :open - Circuit tripped, requests fail fast with CircuitBreakerOpenError

  • :half_open - Testing if service recovered, allows limited requests

Examples:

Basic usage

breaker = HTM::CircuitBreaker.new(name: 'embedding')
result = breaker.call { external_api_call }

With custom thresholds

breaker = HTM::CircuitBreaker.new(
  name: 'tag_extraction',
  failure_threshold: 3,
  reset_timeout: 30
)

Constant Summary collapse

DEFAULT_FAILURE_THRESHOLD =

Default configuration

5
DEFAULT_RESET_TIMEOUT =

Failures before opening circuit

60
DEFAULT_HALF_OPEN_MAX_CALLS =

Seconds before trying half-open

3

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, failure_threshold: DEFAULT_FAILURE_THRESHOLD, reset_timeout: DEFAULT_RESET_TIMEOUT, half_open_max_calls: DEFAULT_HALF_OPEN_MAX_CALLS) ⇒ CircuitBreaker

Initialize a new circuit breaker

Parameters:

  • name (String)

    Identifier for this circuit breaker (for logging)

  • failure_threshold (Integer) (defaults to: DEFAULT_FAILURE_THRESHOLD)

    Number of failures before opening circuit

  • reset_timeout (Integer) (defaults to: DEFAULT_RESET_TIMEOUT)

    Seconds to wait before attempting recovery

  • half_open_max_calls (Integer) (defaults to: DEFAULT_HALF_OPEN_MAX_CALLS)

    Successful calls needed to close circuit



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/htm/circuit_breaker.rb', line 42

def initialize(
  name:,
  failure_threshold: DEFAULT_FAILURE_THRESHOLD,
  reset_timeout: DEFAULT_RESET_TIMEOUT,
  half_open_max_calls: DEFAULT_HALF_OPEN_MAX_CALLS
)
  @name = name
  @failure_threshold = failure_threshold
  @reset_timeout = reset_timeout
  @half_open_max_calls = half_open_max_calls

  @state = :closed
  @failure_count = 0
  @success_count = 0
  @last_failure_time = nil
  @mutex = Mutex.new
end

Instance Attribute Details

#failure_countObject (readonly)

Returns the value of attribute failure_count.



28
29
30
# File 'lib/htm/circuit_breaker.rb', line 28

def failure_count
  @failure_count
end

#last_failure_timeObject (readonly)

Returns the value of attribute last_failure_time.



28
29
30
# File 'lib/htm/circuit_breaker.rb', line 28

def last_failure_time
  @last_failure_time
end

#nameObject (readonly)

Returns the value of attribute name.



28
29
30
# File 'lib/htm/circuit_breaker.rb', line 28

def name
  @name
end

#stateObject (readonly)

Returns the value of attribute state.



28
29
30
# File 'lib/htm/circuit_breaker.rb', line 28

def state
  @state
end

Instance Method Details

#call { ... } ⇒ Object

Execute a block with circuit breaker protection

Yields:

  • Block containing the protected operation

Returns:

  • (Object)

    Result of the block if successful

Raises:

  • (CircuitBreakerOpenError)

    If circuit is open

  • (StandardError)

    If the block raises an error (after recording failure)



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/htm/circuit_breaker.rb', line 67

def call
  @mutex.synchronize do
    case @state
    when :open
      check_reset_timeout
      if @state == :open
        HTM.logger.warn "CircuitBreaker[#{@name}]: Circuit is OPEN, failing fast"
        raise CircuitBreakerOpenError, "Circuit breaker '#{@name}' is open. Service unavailable."
      end
    end
  end

  begin
    result = yield
    record_success
    result
  rescue StandardError => e
    record_failure(e)
    raise
  end
end

#closed?Boolean

Check if circuit is currently closed (normal operation)

Returns:

  • (Boolean)

    true if circuit is closed



101
102
103
# File 'lib/htm/circuit_breaker.rb', line 101

def closed?
  @mutex.synchronize { @state == :closed }
end

#half_open?Boolean

Check if circuit is in half-open state (testing recovery)

Returns:

  • (Boolean)

    true if circuit is half-open



109
110
111
# File 'lib/htm/circuit_breaker.rb', line 109

def half_open?
  @mutex.synchronize { @state == :half_open }
end

#open?Boolean

Check if circuit is currently open

Returns:

  • (Boolean)

    true if circuit is open



93
94
95
# File 'lib/htm/circuit_breaker.rb', line 93

def open?
  @mutex.synchronize { @state == :open }
end

#reset!void

This method returns an undefined value.

Manually reset the circuit breaker to closed state



117
118
119
120
121
122
123
124
125
# File 'lib/htm/circuit_breaker.rb', line 117

def reset!
  @mutex.synchronize do
    @state = :closed
    @failure_count = 0
    @success_count = 0
    @last_failure_time = nil
    HTM.logger.info "CircuitBreaker[#{@name}]: Manually reset to CLOSED"
  end
end

#statsHash

Get current circuit breaker statistics

Returns:

  • (Hash)

    Statistics including state, failure count, etc.



131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/htm/circuit_breaker.rb', line 131

def stats
  @mutex.synchronize do
    {
      name: @name,
      state: @state,
      failure_count: @failure_count,
      success_count: @success_count,
      last_failure_time: @last_failure_time,
      failure_threshold: @failure_threshold,
      reset_timeout: @reset_timeout
    }
  end
end