Module: BreakerMachines::DSL

Extended by:
ActiveSupport::Concern
Included in:
CircuitGroup
Defined in:
lib/breaker_machines/dsl.rb,
lib/breaker_machines/dsl/hedged_builder.rb,
lib/breaker_machines/dsl/circuit_builder.rb,
lib/breaker_machines/dsl/cascading_circuit_builder.rb,
lib/breaker_machines/dsl/parallel_fallback_wrapper.rb

Overview

DSL module for adding circuit breakers to classes

This module uses WeakRef to track instances that include the DSL. Why? In long-running applications (web servers, background workers), objects that include this DSL may be created and destroyed frequently. Without WeakRef, the registry would hold strong references to these objects, preventing garbage collection and causing memory leaks.

Example scenario: A Rails controller that includes BreakerMachines::DSL is instantiated for each request. Without WeakRef, every controller instance would be kept in memory forever.

Defined Under Namespace

Classes: CascadingCircuitBuilder, CircuitBuilder, HedgedBuilder, ParallelFallbackWrapper

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Use included callback to add instance tracking



156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/breaker_machines/dsl.rb', line 156

def self.included(base)
  super

  # Hook into new to register instances
  base.singleton_class.prepend(Module.new do
    def new(...)
      instance = super
      instance_registry << WeakRef.new(instance)
      instance
    end
  end)
end

Instance Method Details

#apply_template(circuit_name, template_name) ⇒ Object

Apply a template to an existing or new circuit

Raises:

  • (ArgumentError)


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

def apply_template(circuit_name, template_name)
  template_config = self.class.circuit_templates[template_name]
  raise ArgumentError, "Template '#{template_name}' not found" unless template_config

  @circuit_instances ||= {}
  @circuit_instances[circuit_name] = Circuit.new(circuit_name, template_config.merge(owner: self))
end

#circuit(name) ⇒ Object



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

def circuit(name)
  self.class.circuits[name] ||= {}
  @circuit_instances ||= {}

  config = self.class.circuits[name].merge(owner: self)
  circuit_type = config.delete(:circuit_type)

  @circuit_instances[name] ||= case circuit_type
                               when :cascading
                                 CascadingCircuit.new(name, config)
                               else
                                 Circuit.new(name, config)
                               end
end

#circuit_instancesObject

Get all circuit instances for this object



249
250
251
# File 'lib/breaker_machines/dsl.rb', line 249

def circuit_instances
  @circuit_instances || {}
end

#circuits_reportObject

Get detailed information for all circuits



259
260
261
# File 'lib/breaker_machines/dsl.rb', line 259

def circuits_report
  circuit_instances.transform_values(&:to_h)
end

#circuits_summaryObject

Get summary of all circuits for this instance



254
255
256
# File 'lib/breaker_machines/dsl.rb', line 254

def circuits_summary
  circuit_instances.transform_values(&:summary)
end

#cleanup_stale_dynamic_circuits(max_age_seconds = 3600) ⇒ Object

Cleanup stale dynamic circuits (global)



279
280
281
# File 'lib/breaker_machines/dsl.rb', line 279

def cleanup_stale_dynamic_circuits(max_age_seconds = 3600)
  BreakerMachines.registry.cleanup_stale_dynamic_circuits(max_age_seconds)
end

#dynamic_circuit(name, template: nil, global: false, &config_block) ⇒ Object

Create a dynamic circuit breaker with inline configuration Options:

global: true - Store circuit globally, preventing memory leaks in long-lived objects
global: false - Store circuit locally in this instance (default, backward compatible)


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/dsl.rb', line 188

def dynamic_circuit(name, template: nil, global: false, &config_block)
  # Start with template config if provided
  base_config = if template && self.class.circuit_templates[template]
                  self.class.circuit_templates[template].deep_dup
                else
                  default_circuit_config
                end

  # Apply additional configuration if block provided
  if config_block
    builder = DSL::CircuitBuilder.new
    builder.instance_variable_set(:@config, base_config.deep_dup)
    builder.instance_eval(&config_block)
    base_config = builder.config
  end

  if global
    # Use global registry to prevent memory leaks
    BreakerMachines.registry.get_or_create_dynamic_circuit(name, self, base_config)
  else
    # Local storage (backward compatible)
    @circuit_instances ||= {}
    @circuit_instances[name] ||= Circuit.new(name, base_config.merge(owner: self))
  end
end

#dynamic_circuit_namesObject

Get all dynamic circuit names from global registry



274
275
276
# File 'lib/breaker_machines/dsl.rb', line 274

def dynamic_circuit_names
  BreakerMachines.registry.dynamic_circuit_names
end

#remove_dynamic_circuit(name) ⇒ Object

Remove a global dynamic circuit by name



269
270
271
# File 'lib/breaker_machines/dsl.rb', line 269

def remove_dynamic_circuit(name)
  BreakerMachines.registry.remove_dynamic_circuit(name)
end

#reset_all_circuitsObject

Reset all circuits for this instance



264
265
266
# File 'lib/breaker_machines/dsl.rb', line 264

def reset_all_circuits
  circuit_instances.each_value(&:hard_reset!)
end