Class: BreakerMachines::Storage::Memory

Inherits:
Base
  • Object
show all
Defined in:
lib/breaker_machines/storage/memory.rb

Overview

High-performance in-memory storage backend with thread-safe operations

WARNING: This storage backend is NOT compatible with DRb (distributed Ruby) environments as memory is not shared between processes. Use Cache backend with an external cache store (Redis, Memcached) for distributed setups.

Instance Method Summary collapse

Constructor Details

#initialize(**options) ⇒ Memory

Returns a new instance of Memory.



14
15
16
17
18
19
20
21
22
# File 'lib/breaker_machines/storage/memory.rb', line 14

def initialize(**options)
  super
  @circuits = Concurrent::Map.new
  @events = Concurrent::Map.new
  @event_logs = Concurrent::Map.new
  @max_events = options[:max_events] || 100
  # Store creation time as anchor for relative timestamps (like Rust implementation)
  @start_time = BreakerMachines.monotonic_time
end

Instance Method Details

#clear(circuit_name) ⇒ Object



58
59
60
61
62
# File 'lib/breaker_machines/storage/memory.rb', line 58

def clear(circuit_name)
  @circuits.delete(circuit_name)
  @events.delete(circuit_name)
  @event_logs.delete(circuit_name)
end

#clear_allObject



64
65
66
67
68
# File 'lib/breaker_machines/storage/memory.rb', line 64

def clear_all
  @circuits.clear
  @events.clear
  @event_logs.clear
end

#event_log(circuit_name, limit) ⇒ Object



89
90
91
92
93
94
# File 'lib/breaker_machines/storage/memory.rb', line 89

def event_log(circuit_name, limit)
  events = @event_logs[circuit_name]
  return [] unless events

  events.last(limit).map(&:dup)
end

#failure_count(circuit_name, window_seconds) ⇒ Object



54
55
56
# File 'lib/breaker_machines/storage/memory.rb', line 54

def failure_count(circuit_name, window_seconds)
  count_events(circuit_name, :failure, window_seconds)
end

#get_status(circuit_name) ⇒ Object



24
25
26
27
28
29
30
31
32
# File 'lib/breaker_machines/storage/memory.rb', line 24

def get_status(circuit_name)
  circuit_data = @circuits[circuit_name]
  return nil unless circuit_data

  BreakerMachines::Status.new(
    status: circuit_data[:status],
    opened_at: circuit_data[:opened_at]
  )
end

#record_event_with_details(circuit_name, type, duration, error: nil, new_state: nil) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/breaker_machines/storage/memory.rb', line 70

def record_event_with_details(circuit_name, type, duration, error: nil, new_state: nil)
  events = @event_logs.compute_if_absent(circuit_name) { Concurrent::Array.new }

  event = {
    type: type,
    timestamp: monotonic_time,
    duration_ms: (duration * 1000).round(2)
  }

  event[:error_class] = error.class.name if error
  event[:error_message] = error.message if error
  event[:new_state] = new_state if new_state

  events << event

  # Keep only the most recent events
  events.shift while events.size > @max_events
end

#record_failure(circuit_name, duration) ⇒ Object



46
47
48
# File 'lib/breaker_machines/storage/memory.rb', line 46

def record_failure(circuit_name, duration)
  record_event(circuit_name, :failure, duration)
end

#record_success(circuit_name, duration) ⇒ Object



42
43
44
# File 'lib/breaker_machines/storage/memory.rb', line 42

def record_success(circuit_name, duration)
  record_event(circuit_name, :success, duration)
end

#set_status(circuit_name, status, opened_at = nil) ⇒ Object



34
35
36
37
38
39
40
# File 'lib/breaker_machines/storage/memory.rb', line 34

def set_status(circuit_name, status, opened_at = nil)
  @circuits[circuit_name] = {
    status: status,
    opened_at: opened_at,
    updated_at: monotonic_time
  }
end

#success_count(circuit_name, window_seconds) ⇒ Object



50
51
52
# File 'lib/breaker_machines/storage/memory.rb', line 50

def success_count(circuit_name, window_seconds)
  count_events(circuit_name, :success, window_seconds)
end

#with_timeout(_timeout_ms) ⇒ Object



96
97
98
99
100
# File 'lib/breaker_machines/storage/memory.rb', line 96

def with_timeout(_timeout_ms)
  # Memory operations should be instant, but we'll still respect the timeout
  # This is more for consistency and to catch any potential deadlocks
  yield
end