Class: Magick::CircuitBreaker

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

Overview

Trips after failure_threshold consecutive-ish failures and then refuses to run the block for timeout seconds, so a dead backend costs one failed call per window instead of one per request.

Two properties the callers depend on:

  • An open circuit RAISES CircuitOpenError rather than returning a falsey value. A breaker that answers false is indistinguishable from a backend that legitimately stored false, and the registry's write path would go on to publish a cache invalidation for a write that never happened — telling every peer to reload the pre-toggle value.
  • Half-open admits exactly ONE probe, and a probe that fails re-opens the circuit immediately. Resetting the failure count on the way into half-open would mean a permanently dead backend absorbs failure_threshold requests per timeout window rather than one.

Constant Summary collapse

DEFAULT_FAILURE_THRESHOLD =
5
DEFAULT_TIMEOUT =
60

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(failure_threshold: DEFAULT_FAILURE_THRESHOLD, timeout: DEFAULT_TIMEOUT) ⇒ CircuitBreaker

Returns a new instance of CircuitBreaker.



25
26
27
28
29
30
31
32
33
# File 'lib/magick/circuit_breaker.rb', line 25

def initialize(failure_threshold: DEFAULT_FAILURE_THRESHOLD, timeout: DEFAULT_TIMEOUT)
  @failure_threshold = failure_threshold
  @timeout = timeout
  @failure_count = 0
  @last_failure_time = nil
  @state = :closed
  @half_open_probe = false
  @mutex = Mutex.new
end

Instance Attribute Details

#failure_count ⇒ Object (readonly)

Returns the value of attribute failure_count.



23
24
25
# File 'lib/magick/circuit_breaker.rb', line 23

def failure_count
  @failure_count
end

#last_failure_time ⇒ Object (readonly)

Returns the value of attribute last_failure_time.



23
24
25
# File 'lib/magick/circuit_breaker.rb', line 23

def last_failure_time
  @last_failure_time
end

Instance Method Details

#call ⇒ Object

Runs the block, or raises CircuitOpenError without touching it.

Raises:



36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/magick/circuit_breaker.rb', line 36

def call
  raise CircuitOpenError, "Circuit is #{state}; refusing to call the backend" unless acquire

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

#open? ⇒ Boolean

Returns:

  • (Boolean)


53
54
55
# File 'lib/magick/circuit_breaker.rb', line 53

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

#state ⇒ Object



49
50
51
# File 'lib/magick/circuit_breaker.rb', line 49

def state
  @mutex.synchronize { current_state }
end