Class: BreakerMachines::Registry

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/breaker_machines/registry.rb

Overview

Global registry for tracking all circuit breaker instances

Instance Method Summary collapse

Constructor Details

#initializeRegistry

Returns a new instance of Registry.



11
12
13
14
15
16
17
# File 'lib/breaker_machines/registry.rb', line 11

def initialize
  @circuits = Concurrent::Map.new
  @named_circuits = Concurrent::Map.new # For dynamic circuits by name
  @mutex = Mutex.new
  @registration_count = 0
  @cleanup_interval = 100 # Clean up every N registrations
end

Instance Method Details

#all_circuitsObject

Get all active circuits



42
43
44
45
46
47
48
49
50
# File 'lib/breaker_machines/registry.rb', line 42

def all_circuits
  @mutex.synchronize do
    @circuits.values.map do |weak_ref|
      weak_ref.__getobj__
    rescue WeakRef::RefError
      nil
    end.compact
  end
end

#all_statsObject

Get all stats with detailed metrics



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/breaker_machines/registry.rb', line 100

def all_stats
  circuits = all_circuits

  {
    summary: stats_summary,
    circuits: circuits.map { |c| c.stats.to_h },
    health: {
      open_count: circuits.count(&:open?),
      closed_count: circuits.count(&:closed?),
      half_open_count: circuits.count(&:half_open?),
      total_failures: circuits.sum { |c| c.stats.failure_count },
      total_successes: circuits.sum { |c| c.stats.success_count }
    }
  }
end

#cleanup_dead_referencesObject

Clean up dead references (thread-safe)



223
224
225
226
227
# File 'lib/breaker_machines/registry.rb', line 223

def cleanup_dead_references
  @mutex.synchronize do
    cleanup_dead_references_unsafe
  end
end

#cleanup_stale_dynamic_circuits(max_age_seconds = 3600) ⇒ Object

Cleanup stale dynamic circuits older than given age



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/breaker_machines/registry.rb', line 183

def cleanup_stale_dynamic_circuits(max_age_seconds = 3600)
  @mutex.synchronize do
    cutoff_time = Time.now - max_age_seconds
    stale_names = []

    @named_circuits.each_pair do |name, weak_ref|
      circuit = weak_ref.__getobj__
      # Check if circuit has a last_activity_time and it's stale
      if circuit.respond_to?(:last_activity_time) &&
         circuit.last_activity_time &&
         circuit.last_activity_time < cutoff_time
        stale_names << name
      end
    rescue WeakRef::RefError
      stale_names << name
    end

    stale_names.each do |name|
      weak_ref = @named_circuits.delete(name)
      begin
        circuit = weak_ref.__getobj__
        @circuits.delete(circuit) if circuit
      rescue WeakRef::RefError
        # Already gone
      end
    end

    stale_names.size
  end
end

#clearObject

Clear all circuits (useful for testing)



215
216
217
218
219
220
# File 'lib/breaker_machines/registry.rb', line 215

def clear
  @mutex.synchronize do
    @circuits.clear
    @named_circuits.clear
  end
end

#detailed_reportObject

Get detailed information for all circuits



117
118
119
# File 'lib/breaker_machines/registry.rb', line 117

def detailed_report
  all_circuits.map(&:to_h)
end

#dynamic_circuit_namesObject

Get all dynamic circuit names



169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/breaker_machines/registry.rb', line 169

def dynamic_circuit_names
  @mutex.synchronize do
    alive_names = []
    @named_circuits.each_pair do |name, weak_ref|
      weak_ref.__getobj__
      alive_names << name
    rescue WeakRef::RefError
      @named_circuits.delete(name)
    end
    alive_names
  end
end

#find(name) ⇒ Object

Find first circuit by name



58
59
60
# File 'lib/breaker_machines/registry.rb', line 58

def find(name)
  find_by_name(name).first
end

#find_by_name(name) ⇒ Object

Find circuits by name



53
54
55
# File 'lib/breaker_machines/registry.rb', line 53

def find_by_name(name)
  all_circuits.select { |circuit| circuit.name == name }
end

#force_close(name) ⇒ Object

Force close a circuit by name



72
73
74
75
76
77
78
# File 'lib/breaker_machines/registry.rb', line 72

def force_close(name) # rubocop:disable Naming/PredicateMethod
  circuits = find_by_name(name)
  return false if circuits.empty?

  circuits.each(&:force_close)
  true
end

#force_open(name) ⇒ Object

Force open a circuit by name



63
64
65
66
67
68
69
# File 'lib/breaker_machines/registry.rb', line 63

def force_open(name) # rubocop:disable Naming/PredicateMethod
  circuits = find_by_name(name)
  return false if circuits.empty?

  circuits.each(&:force_open)
  true
end

#get_or_create_dynamic_circuit(name, owner, config) ⇒ Object

Get or create a globally managed dynamic circuit



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/breaker_machines/registry.rb', line 122

def get_or_create_dynamic_circuit(name, owner, config)
  @mutex.synchronize do
    # Check if circuit already exists and is still alive
    if @named_circuits.key?(name)
      weak_ref = @named_circuits[name]
      begin
        existing_circuit = weak_ref.__getobj__
        return existing_circuit if existing_circuit
      rescue WeakRef::RefError
        # Circuit was garbage collected, remove the stale reference
        @named_circuits.delete(name)
      end
    end

    # Create new circuit with weak owner reference
    # Don't auto-register to avoid deadlock
    weak_owner = owner.is_a?(WeakRef) ? owner : WeakRef.new(owner)
    circuit_config = config.merge(owner: weak_owner, auto_register: false)
    new_circuit = Circuit.new(name, circuit_config)

    # Manually register the circuit (we're already in sync block)
    @circuits[new_circuit] = WeakRef.new(new_circuit)
    @named_circuits[name] = WeakRef.new(new_circuit)

    new_circuit
  end
end

#register(circuit) ⇒ Object

Register a circuit instance



20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/breaker_machines/registry.rb', line 20

def register(circuit)
  @mutex.synchronize do
    # Use circuit object as key - Concurrent::Map handles object identity correctly
    @circuits[circuit] = WeakRef.new(circuit)

    # Periodic cleanup
    @registration_count += 1
    if @registration_count >= @cleanup_interval
      cleanup_dead_references_unsafe
      @registration_count = 0
    end
  end
end

#remove_dynamic_circuit(name) ⇒ Object

Remove a dynamic circuit by name



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/breaker_machines/registry.rb', line 151

def remove_dynamic_circuit(name)
  @mutex.synchronize do
    if @named_circuits.key?(name)
      weak_ref = @named_circuits.delete(name)
      begin
        circuit = weak_ref.__getobj__
        @circuits.delete(circuit) if circuit
        true
      rescue WeakRef::RefError
        false
      end
    else
      false
    end
  end
end

#reset(name) ⇒ Object

Reset a circuit by name



81
82
83
84
85
86
87
# File 'lib/breaker_machines/registry.rb', line 81

def reset(name) # rubocop:disable Naming/PredicateMethod
  circuits = find_by_name(name)
  return false if circuits.empty?

  circuits.each(&:reset)
  true
end

#stats_summaryObject

Get summary statistics



90
91
92
93
94
95
96
97
# File 'lib/breaker_machines/registry.rb', line 90

def stats_summary
  circuits = all_circuits
  {
    total: circuits.size,
    by_state: circuits.group_by(&:status_name).transform_values(&:count),
    by_name: circuits.group_by(&:name).transform_values(&:count)
  }
end

#unregister(circuit) ⇒ Object

Unregister a circuit instance



35
36
37
38
39
# File 'lib/breaker_machines/registry.rb', line 35

def unregister(circuit)
  @mutex.synchronize do
    @circuits.delete(circuit)
  end
end