Announcing Stoplight, a Ruby circuit breaker gem

Posted February 20, 2015 by Taylor Fausak

I am proud to announce the first stable release of Stoplight! It's like traffic control for your code. Stoplight implements the circuit breaker design pattern in Ruby. Use it to gracefully handle things that occasionally fail.

To get started, install the stoplight gem.

$ gem install stoplight --version '~> 1.0'

Once you've done that, use the Stoplight method to create stoplights. Each stoplight needs a name and a block to run. Here's a simple example that calculates a rough approximation of pi.

require 'stoplight'

stoplight = Stoplight('roughly-pi') { 22.0 / 7.0 }
stoplight.run
# => 3.142857142857143
stoplight.color
# => "green"

Stoplights start off green. If they fail enough, they switch to red. When they're red, they short circuit and raise an error. Here's an example that will never succeed.

stoplight = Stoplight('no-conversion') { 'oh'[:no] }
stoplight.run
# TypeError: no implicit conversion of Symbol into Integer
stoplight.run
# TypeError: no implicit conversion of Symbol into Integer
stoplight.run
# Switching no-conversion from green to red because TypeError no implicit conversion of Symbol into Integer
# TypeError: no implicit conversion of Symbol into Integer
stoplight.color
# => "red"
stoplight.run
# Stoplight::Error::RedLight: no-conversion

By default, stoplights pass errors through them. When they're red, they'll raise a red light error. Sometimes it makes sense to return a default value instead. You can do this by using a fallback. If the stoplight is green and raises an error, it'll run the fallback. If the stoplight is red, it'll run the fallback instead of raising an error. Here's an example that uses a fallback.

stoplight = Stoplight('zero') { 1 / 0 }.with_fallback { 0 }
stoplight.run
# => 0
stoplight.run
# => 0
stoplight.run
# Switching zero from green to red because ZeroDivisionError divided by 0
# => 0
stoplight.color
# => "red"
stoplight.run
# => 0

These examples scratch the surface of what stoplights can do. They are highly configurable. Check out the readme for more examples and settings.

Motivation

Six months ago, OrgSync experienced a few hours of downtime. It started with a few external services taking longer than expected to respond. Those delays created a negative feedback loop that eventually brought the site down.

We decided to wrap those external services in circuit breakers to protect ourselves from these types of failures. We knew of a few circuit breaker gems and looked for more. We had a few requirements to meet:

We evaluated all of the gems we could find, but ultimately none of them were right for us. So Cameron Desautels and I started developing Stoplight. Those other gems did inspire us, though. I'll briefly cover them here. You might find one of them useful if Stoplight isn't right for you.

Inspiration

Breaker

Breaker was created by Adam Hawkins. It has a lot in common with Stoplight. It's simple, has great documentation, and supports external data stores. Unfortunately there's no interface for the data stores, so you have no choice but to read the reference implementation.

require 'breaker'

Breaker.circuit('example').run { p true }
# true
# => true

CircuitB

Aleksey Gureev created CircuitB. It also supports external data stores. Unfortunately it requires configuring circuits ahead of time. It also doesn't return the result of the block (although that will be fixed in version 1.2). Those were deal breakers for us, so we couldn't use it.

require 'circuit_b'

CircuitB.configure do |c|
  c.state_storage = CircuitB::Storage::Memory.new
  c.fuse 'example'
end

CircuitB('example') { p true }
# true
# => 0

CircuitBreaker

Will Sargent at Typesafe created CircuitBreaker. It actually implements a state machine behind the scenes. That makes it easy to debug, but hard to use with external data stores. It's also intended to be used as a mixin, which isn't what we had in mind.

require 'circuit_breaker'

state = CircuitBreaker::CircuitState.new
handler = CircuitBreaker::CircuitHandler.new
handler.handle(state, -> { p true })
# true
# => true

Circuitbox

Yann Armand at Yammer created Circuitbox. Of all the circuit breaker gems, it feels the most heavyweight. It depends on ActiveSupport, which provides external data stores and logging. It also supports advanced percentage-based heuristics.

require 'circuitbox'

Circuitbox.circuit(:example).run { p true }
# D, [2015-02-03T09:05:04.307606 #1128] DEBUG -- : [CIRCUIT] closed: querying example
# true
# D, [2015-02-03T09:05:04.307920 #1128] DEBUG -- : [CIRCUIT] closed: example querie success
# => true

SimpleCircuitBreaker

Julius Volz and Tobias Schmidt at SoundCloud created SimpleCircuitBreaker. It definitely lives up to its name. At less than 60 lines of code, it's the simplest circuit breaker gem available. This is probably how every other gem started out. Because of its simplicity, it doesn't support external data stores.

require 'simple_circuit_breaker'

SimpleCircuitBreaker.new(3, 10).handle { p true }
# true
# => true

YaCircuitBreaker

Patrick Huesler at Wooga created YaCircuitBreaker. It's one of the few that allows you to manually manage the state with #trip! and #reset!. Unfortunately it has a few problems. It doesn't support external data stores, the gem name (ya_circuit_breaker) isn't what you require (circuit_breaker), and it doesn't return the result of the block.

require 'circuit_breaker'

CircuitBreaker::Basic.new.execute { p true }
# true
# => nil

[This post was originally posted to my blog.]


Taylor Fausak was born in California, but he got to Texas as soon as he could. He studied Computer Science at the University of Texas at Austin before entering the wild world of software development. After a brief stint at Cisco, he started his career at Famigo working on all aspects of web development. Then he swapped his Django experience for a chunky slice of Rails bacon and joined OrgSync in the fall of 2012. When he's not slinging code around, he likes riding bikes, playing Magic, and throwing frisbees.


comments powered by Disqus