Circuitbox Documentation

repository·main·Indexed 20 days ago

https://github.com/yammer/circuitbox

A Ruby gem implementing the circuit breaker pattern to protect applications from failing service dependencies by monitoring error rates and temporarily halting requests. Features include configurable error and volume thresholds, support for Moneta and MemoryStore for state persistence, and integration with ActiveSupport::Notifications for circuit lifecycle events.

Tokens
7.9K
Snippets
22
Records
34
Agent score
67%

What's inside circuitbox

  1. Circuit Store changes and Moneta requirements

    main
    • Default Store: The default circuit store has changed from Moneta's memory store to Circuitbox::MemoryStore. Circuitbox::MemoryStore periodically removes expired keys to allow Ruby to reclaim memory.
    • Moneta Users: If using Moneta as a circuit store, you must use adapters that support bulk read functionality.
    • Time Class: When using Circuitbox::MemoryStore, the circuit's time class is Circuitbox::TimeHelper::Monotonic.
  2. How Circuit Notifications work

    main

    Circuitbox can emit notifications when specific circuit events occur. This allows you to hook into the circuit lifecycle for logging, monitoring, or timing.

    There are two built-in notifier types:

    1. null: Does nothing.
    2. active support: Sends notifications via ActiveSupport::Notifications.

    Events that trigger notifications include:

    • Circuit block runs (used for timing)
    • Circuit is skipped (when the circuit is already open)
    • Circuit run is successful
    • Circuit run is failed
    • Circuit is opened
    • Circuit is closed
    • Circuit is not configured correctly
  3. Requirements for Moneta circuit stores

    main

    If you use a Moneta store (instead of the default MemoryStore) to persist circuit state across processes, ensure the chosen store supports:

    • increment operations
    • Key expiry
    • Bulk read operations
    • Concurrent access (if shared between processes)
  4. Reset circuits in tests

    main

    The class-level Circuitbox.reset and Circuitbox::CircuitBreaker.reset methods have been removed. To reset persisted state (e.g., in a MemoryStore) between tests to prevent leakage, reconfigure Circuitbox with a new store instance.

    Circuitbox.configure do |config|
      # Reset persisted state in the memory store so it doesn't leak between tests
      config.default_circuit_store = Circuitbox::MemoryStore.new
    end
  5. Use Circuitbox to wrap service calls

    main

    You can wrap external service calls using Circuitbox.circuit.

    By default, Circuitbox.circuit returns nil if the circuit is open or the request fails. You can specify which exceptions should be tracked to count towards the failure rate using the exceptions option.

    To change this behavior and have the circuit throw an exception instead of returning nil, use the .run method on the circuit object.

    # Returns nil on failure or open circuit
    Circuitbox.circuit(:your_service, exceptions: [Net::ReadTimeout]) do
      Net::HTTP.get URI('http://example.com/api/messages')
    end
    
    # Throws an exception on failure or open circuit
    circuit = Circuitbox.circuit(:your_service, exceptions: [Net::ReadTimeout])
    circuit.run do
      Net::HTTP.get URI('http://example.com/api/messages')
    end
    
    # Alternatively, use .run with exception: false to return nil
    circuit.run(exception: false) do
      # ...
    end
  6. Install Circuitbox

    main

    To use Circuitbox in your Ruby application, add it to your Gemfile and run bundle, or install it directly via the gem command.

    # In your Gemfile
    gem 'circuitbox'
    $ bundle
    # OR
    $ gem install circuitbox
  7. Configure settings per circuit

    main

    When calling Circuitbox.circuit, you can pass an options hash to override global defaults for that specific service. You can also pass a Proc as an option value to allow dynamic configuration without restarting processes.

    Available per-circuit options:

    • exceptions: (Required) Array of exception classes to track for counting failures.
    • sleep_window: Seconds the circuit stays open once it has passed the error threshold.
    • time_window: Length of interval (in seconds) over which the error rate is calculated.
    • volume_threshold: Number of requests within time_window required before calculating error rates.
    • circuit_store: The store to save circuit state (overrides default_circuit_store). Must be Moneta compatible and support increment.
    • error_threshold: Percentage (0-100) of failures that will open the circuit.
    • notifier: Custom notifier (overrides default_notifier).
    Circuitbox.circuit(:your_service, {
      exceptions:       [YourCustomException],
      sleep_window:     300,
      time_window:      60,
      volume_threshold: 10,
      circuit_store:    Circuitbox::MemoryStore.new,
      error_threshold:  50,
      notifier:         Notifier.new
    })
    
    # Dynamic configuration using a Proc
    Circuitbox.circuit(:yammer, {
      sleep_window: Proc.new { Configuration.get(:sleep_window) },
      exceptions: [Net::ReadTimeout]
    })
  8. Configure Circuitbox globally

    main

    Use Circuitbox.configure to set default settings for all circuits. Note that calling this method clears the internal circuit cache. Any circuits manually created via Circuitbox::CircuitBreaker before configuration must be recreated to use the new defaults.

    Available global configuration keys:

    • default_circuit_store: The store used to save circuit state. Defaults to Circuitbox::MemoryStore. Must be Moneta compatible.
    • default_notifier: The notifier for circuit events. Defaults to Circuitbox::Notifier::ActiveSupport (if available) or Circuitbox::Notifier::Null.
    Circuitbox.configure do |config|
      config.default_circuit_store = Circuitbox::MemoryStore.new
      config.default_notifier = Circuitbox::Notifier::Null.new
    end
  9. Configure Circuitbox defaults

    main

    Use Circuitbox.configure to set global defaults for the circuit store and the notifier. When you call configure, any previously cached circuits are cleared to ensure the new configuration is applied.

    Inside the configuration block, you can set:

    • default_circuit_store: The store used by circuits that do not have a specific store assigned. Defaults to Circuitbox::MemoryStore.
    • default_notifier: The notifier used by circuits that do not have a specific notifier assigned. If ActiveSupport::Notifications is defined, it defaults to Circuitbox::Notifier::ActiveSupport; otherwise, it defaults to Circuitbox::Notifier::Null.
    Circuitbox.configure do |config| 
      config.default_circuit_store = MyCustomStore.new
      config.default_notifier = MyCustomNotifier.new
    end
  10. Subscribe to circuit state changes (Open/Close)

    main

    You can use ActiveSupport::Notifications.subscribe to listen for state transitions like opening or closing a circuit. The payload contains the :circuit name.

    ActiveSupport::Notifications.subscribe('open.circuitbox') do |*args|
      event = ActiveSupport::Notifications::Event.new(*args)
      circuit_name = event.payload[:circuit]
      Rails.logger.warn("Open circuit for: #{circuit_name}")
    end
    
    ActiveSupport::Notifications.subscribe('close.circuitbox') do |*args|
      event = ActiveSupport::Notifications::Event.new(*args)
      circuit_name = event.payload[:circuit]
      Rails.logger.info("Close circuit for: #{circuit_name}")
    end
  11. Subscribe to circuit configuration warnings

    main

    To catch misconfigurations, subscribe to warning.circuitbox. The payload provides the :circuit name and a :message describing the issue.

    ActiveSupport::Notifications.subscribe('warning.circuitbox') do |*args|
      event = ActiveSupport::Notifications::Event.new(*args)
      circuit_name = event.payload[:circuit]
      warning      = event.payload[:message]
      Rails.logger.warning("Circuit warning for: #{circuit_name} Message: #{warning}")
    end