circuitbox 0.11.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -4
- data/benchmark/circuit_store_benchmark.rb +0 -2
- data/lib/circuitbox.rb +1 -9
- data/lib/circuitbox/circuit_breaker.rb +3 -35
- data/lib/circuitbox/faraday_middleware.rb +11 -2
- data/lib/circuitbox/version.rb +1 -1
- data/test/circuitbox_test.rb +0 -18
- data/test/faraday_middleware_test.rb +25 -3
- data/test/integration/circuitbox_cross_process_open_test.rb +6 -5
- metadata +2 -3
- data/lib/tasks/circuits.rake +0 -12
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 38c8cdbfef9e769dd2ad65c59905a6c7f1c9bad1
         | 
| 4 | 
            +
              data.tar.gz: e7ed610ab1231ff949b4549a6cec911b61690431
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 82ba7ae27fabb672864ba38c4678c0b37a2bb08104103fc67ed3f8eb9f36973105d6fe5eae03d8dcd2e42f297371f7e87775b8ddf409ea35e0ddddcdce57a0b7
         | 
| 7 | 
            +
              data.tar.gz: b7b4fe13bb21d1978e620b481d188f475b78b4e9809ca59f6f9ef5b12839114b3f358e0136a6ab3e23a2e993358c5701a0d9f962cf010d8bd735d1614aa61956
         | 
    
        data/README.md
    CHANGED
    
    | @@ -190,7 +190,7 @@ Circuitbox.circuit :identifier, cache: Moneta.new(:Memory) | |
| 190 190 |  | 
| 191 191 | 
             
            ### LMDB
         | 
| 192 192 |  | 
| 193 | 
            -
            An persisted directory backed store, which is thread and multi process  | 
| 193 | 
            +
            An persisted directory backed store, which is thread and multi process safe.
         | 
| 194 194 | 
             
            depends on the `lmdb` gem. It is slower than Memory or Daybreak, but can be
         | 
| 195 195 | 
             
            used in multi thread and multi process environments like like Puma.
         | 
| 196 196 |  | 
| @@ -204,7 +204,7 @@ Circuitbox.circuit :identifier, cache: Moneta.new(:LMDB, dir: "./", db: "mydb") | |
| 204 204 | 
             
            An persisted file backed store, which comes with the ruby
         | 
| 205 205 | 
             
            [stdlib](http://ruby-doc.org/stdlib-2.3.0/libdoc/pstore/rdoc/PStore.html). It
         | 
| 206 206 | 
             
            has no external dependecies and works on every ruby implementation. Due to it
         | 
| 207 | 
            -
            being file backed it is multi process  | 
| 207 | 
            +
            being file backed it is multi process safe, good for development using Unicorn.
         | 
| 208 208 |  | 
| 209 209 | 
             
            ```ruby
         | 
| 210 210 | 
             
            Circuitbox.circuit :identifier, cache: Moneta.new(:PStore, file: "db.pstore")
         | 
| @@ -212,7 +212,7 @@ Circuitbox.circuit :identifier, cache: Moneta.new(:PStore, file: "db.pstore") | |
| 212 212 |  | 
| 213 213 | 
             
            ### Daybreak
         | 
| 214 214 |  | 
| 215 | 
            -
            Persisted, file backed key value store in pure ruby. It is process  | 
| 215 | 
            +
            Persisted, file backed key value store in pure ruby. It is process safe and
         | 
| 216 216 | 
             
            outperforms most other stores in circuitbox. This is recommended for production
         | 
| 217 217 | 
             
            use with Unicorn. It depends on the `daybreak` gem.
         | 
| 218 218 |  | 
| @@ -295,8 +295,9 @@ c.use Circuitbox::FaradayMiddleware, open_circuit: lambda { |response| response. | |
| 295 295 | 
             
            ```
         | 
| 296 296 |  | 
| 297 297 | 
             
            ## CHANGELOG
         | 
| 298 | 
            -
             | 
| 299 298 | 
             
            ### version next
         | 
| 299 | 
            +
             | 
| 300 | 
            +
            ### v0.11.0
         | 
| 300 301 | 
             
            - fix URI require missing (https://github.com/yammer/circuitbox/pull/42 @gottfrois)
         | 
| 301 302 | 
             
            - configurable circuitbox store backend via Moneta supporting multi process circuits
         | 
| 302 303 |  | 
    
        data/lib/circuitbox.rb
    CHANGED
    
    | @@ -16,7 +16,7 @@ require 'circuitbox/errors/open_circuit_error' | |
| 16 16 | 
             
            require 'circuitbox/errors/service_failure_error'
         | 
| 17 17 |  | 
| 18 18 | 
             
            class Circuitbox
         | 
| 19 | 
            -
              attr_accessor :circuits, :circuit_store | 
| 19 | 
            +
              attr_accessor :circuits, :circuit_store
         | 
| 20 20 | 
             
              cattr_accessor :configure
         | 
| 21 21 |  | 
| 22 22 | 
             
              def self.instance
         | 
| @@ -44,14 +44,6 @@ class Circuitbox | |
| 44 44 | 
             
                self.instance.circuit_store = store
         | 
| 45 45 | 
             
              end
         | 
| 46 46 |  | 
| 47 | 
            -
              def self.stat_store
         | 
| 48 | 
            -
                self.instance.stat_store
         | 
| 49 | 
            -
              end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
              def self.stat_store=(store)
         | 
| 52 | 
            -
                self.instance.stat_store = store
         | 
| 53 | 
            -
              end
         | 
| 54 | 
            -
             | 
| 55 47 | 
             
              def self.[](service_identifier, options = {})
         | 
| 56 48 | 
             
                self.circuit(service_identifier, options)
         | 
| 57 49 | 
             
              end
         | 
| @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            class Circuitbox
         | 
| 2 2 | 
             
              class CircuitBreaker
         | 
| 3 3 | 
             
                attr_accessor :service, :circuit_options, :exceptions, :partition,
         | 
| 4 | 
            -
                              :logger, : | 
| 4 | 
            +
                              :logger, :circuit_store, :notifier
         | 
| 5 5 |  | 
| 6 6 | 
             
                DEFAULTS = {
         | 
| 7 7 | 
             
                  sleep_window:     300,
         | 
| @@ -31,7 +31,6 @@ class Circuitbox | |
| 31 31 | 
             
                  @exceptions = [Timeout::Error] if @exceptions.blank?
         | 
| 32 32 |  | 
| 33 33 | 
             
                  @logger     = defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
         | 
| 34 | 
            -
                  @stat_store = options.fetch(:stat_store) { Circuitbox.stat_store }
         | 
| 35 34 | 
             
                  @time_class   = options.fetch(:time_class) { Time }
         | 
| 36 35 | 
             
                  sanitize_options
         | 
| 37 36 | 
             
                end
         | 
| @@ -91,26 +90,6 @@ class Circuitbox | |
| 91 90 | 
             
                  end
         | 
| 92 91 | 
             
                end
         | 
| 93 92 |  | 
| 94 | 
            -
                def stats(partition)
         | 
| 95 | 
            -
                  @partition = partition
         | 
| 96 | 
            -
                  options = { without_partition: @partition.blank? }
         | 
| 97 | 
            -
             | 
| 98 | 
            -
                  stats = []
         | 
| 99 | 
            -
                  end_time = Time.now
         | 
| 100 | 
            -
                  hour = 48.hours.ago.change(min: 0, sec: 0)
         | 
| 101 | 
            -
                  while hour <= end_time
         | 
| 102 | 
            -
                    time_object = hour
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                    60.times do |i|
         | 
| 105 | 
            -
                      time = time_object.change(min: i, sec: 0).to_i
         | 
| 106 | 
            -
                      stats << stats_for_time(time, options) unless time > Time.now.to_i
         | 
| 107 | 
            -
                    end
         | 
| 108 | 
            -
             | 
| 109 | 
            -
                    hour += 3600
         | 
| 110 | 
            -
                  end
         | 
| 111 | 
            -
                  stats
         | 
| 112 | 
            -
                end
         | 
| 113 | 
            -
             | 
| 114 93 | 
             
                def error_rate(failures = failure_count, success = success_count)
         | 
| 115 94 | 
             
                  all_count = failures + success
         | 
| 116 95 | 
             
                  return 0.0 unless all_count > 0
         | 
| @@ -194,11 +173,6 @@ class Circuitbox | |
| 194 173 | 
             
                def log_event(event)
         | 
| 195 174 | 
             
                  notifier.new(service,partition).notify(event)
         | 
| 196 175 | 
             
                  log_event_to_process(event)
         | 
| 197 | 
            -
             | 
| 198 | 
            -
                  if stat_store.present?
         | 
| 199 | 
            -
                    log_event_to_stat_store(stat_storage_key(event))
         | 
| 200 | 
            -
                    log_event_to_stat_store(stat_storage_key(event, without_partition: true))
         | 
| 201 | 
            -
                  end
         | 
| 202 176 | 
             
                end
         | 
| 203 177 |  | 
| 204 178 | 
             
                def log_metrics(error_rate, failures, successes)
         | 
| @@ -217,7 +191,7 @@ class Circuitbox | |
| 217 191 | 
             
                  end
         | 
| 218 192 | 
             
                end
         | 
| 219 193 |  | 
| 220 | 
            -
                # When there is a successful response within a  | 
| 194 | 
            +
                # When there is a successful response within a count interval, clear the failures.
         | 
| 221 195 | 
             
                def clear_failures!
         | 
| 222 196 | 
             
                  circuit_store.store(stat_storage_key(:failure), 0, raw: true)
         | 
| 223 197 | 
             
                end
         | 
| @@ -251,6 +225,7 @@ class Circuitbox | |
| 251 225 | 
             
                  storage_key(:stats, align_time_on_minute, event, options)
         | 
| 252 226 | 
             
                end
         | 
| 253 227 |  | 
| 228 | 
            +
             | 
| 254 229 | 
             
                # return time representation in seconds
         | 
| 255 230 | 
             
                def align_time_on_minute(time=nil)
         | 
| 256 231 | 
             
                  time      ||= @time_class.now.to_i
         | 
| @@ -278,12 +253,5 @@ class Circuitbox | |
| 278 253 | 
             
                  Circuitbox.reset
         | 
| 279 254 | 
             
                end
         | 
| 280 255 |  | 
| 281 | 
            -
                def stats_for_time(time, options = {})
         | 
| 282 | 
            -
                  stats = { time: align_time_on_minute(time) }
         | 
| 283 | 
            -
                  [:success, :failure, :open].each do |event|
         | 
| 284 | 
            -
                    stats[event] = stat_store.read(storage_key(:stats, time, event, options), raw: true) || 0
         | 
| 285 | 
            -
                  end
         | 
| 286 | 
            -
                  stats
         | 
| 287 | 
            -
                end
         | 
| 288 256 | 
             
              end
         | 
| 289 257 | 
             
            end
         | 
| @@ -21,10 +21,18 @@ class Circuitbox | |
| 21 21 |  | 
| 22 22 | 
             
                attr_reader :opts
         | 
| 23 23 |  | 
| 24 | 
            +
                DEFAULT_CIRCUITBOX_OPTIONS = {
         | 
| 25 | 
            +
                  open_circuit: lambda do |response|
         | 
| 26 | 
            +
                    # response.status:
         | 
| 27 | 
            +
                    # nil -> connection could not be established, or failed very hard
         | 
| 28 | 
            +
                    # 5xx -> non recoverable server error, oposed to 4xx which are client errors
         | 
| 29 | 
            +
                    response.status.nil? || (500 <= response.status && response.status <= 599)
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                }
         | 
| 32 | 
            +
             | 
| 24 33 | 
             
                def initialize(app, opts = {})
         | 
| 25 34 | 
             
                  @app = app
         | 
| 26 | 
            -
                   | 
| 27 | 
            -
                  @opts = default_options.merge(opts)
         | 
| 35 | 
            +
                  @opts = DEFAULT_CIRCUITBOX_OPTIONS.merge(opts)
         | 
| 28 36 | 
             
                  super(app)
         | 
| 29 37 | 
             
                end
         | 
| 30 38 |  | 
| @@ -101,5 +109,6 @@ class Circuitbox | |
| 101 109 | 
             
                  id = identifier.respond_to?(:call) ? identifier.call(env) : identifier
         | 
| 102 110 | 
             
                  circuitbox.circuit id, circuit_breaker_options
         | 
| 103 111 | 
             
                end
         | 
| 112 | 
            +
             | 
| 104 113 | 
             
              end
         | 
| 105 114 | 
             
            end
         | 
    
        data/lib/circuitbox/version.rb
    CHANGED
    
    
    
        data/test/circuitbox_test.rb
    CHANGED
    
    | @@ -6,16 +6,6 @@ describe Circuitbox do | |
| 6 6 | 
             
              before { Circuitbox.reset }
         | 
| 7 7 | 
             
              after { Circuitbox.reset }
         | 
| 8 8 |  | 
| 9 | 
            -
              describe "Circuitbox.configure" do
         | 
| 10 | 
            -
                it "configures instance variables on init" do
         | 
| 11 | 
            -
                  Circuitbox.configure do
         | 
| 12 | 
            -
                    self.stat_store = "hello"
         | 
| 13 | 
            -
                  end
         | 
| 14 | 
            -
             | 
| 15 | 
            -
                  assert_equal "hello", Circuitbox.stat_store
         | 
| 16 | 
            -
                end
         | 
| 17 | 
            -
              end
         | 
| 18 | 
            -
             | 
| 19 9 | 
             
              describe "Circuitbox.circuit_store" do
         | 
| 20 10 | 
             
                it "is configurable" do
         | 
| 21 11 | 
             
                  example_store = Circuitbox::ExampleStore.new
         | 
| @@ -24,14 +14,6 @@ describe Circuitbox do | |
| 24 14 | 
             
                end
         | 
| 25 15 | 
             
              end
         | 
| 26 16 |  | 
| 27 | 
            -
              describe "Circuitbox.stat_store" do
         | 
| 28 | 
            -
                it "is configurable" do
         | 
| 29 | 
            -
                  example_store = Circuitbox::ExampleStore.new
         | 
| 30 | 
            -
                  Circuitbox.stat_store = example_store
         | 
| 31 | 
            -
                  assert_equal example_store, Circuitbox[:yammer].stat_store
         | 
| 32 | 
            -
                end
         | 
| 33 | 
            -
              end
         | 
| 34 | 
            -
             | 
| 35 17 | 
             
              describe "Circuitbox[:service]" do
         | 
| 36 18 | 
             
                it "delegates to #circuit" do
         | 
| 37 19 | 
             
                  Circuitbox.expects(:circuit).with(:yammer, {})
         | 
| @@ -64,21 +64,43 @@ class Circuitbox | |
| 64 64 | 
             
                def test_overridde_success_response
         | 
| 65 65 | 
             
                  env = { url: "url" }
         | 
| 66 66 | 
             
                  app = gimme
         | 
| 67 | 
            -
                  give(app).call(anything) { Faraday::Response.new(status:  | 
| 68 | 
            -
                  error_response = lambda { |response|  | 
| 67 | 
            +
                  give(app).call(anything) { Faraday::Response.new(status: 500) }
         | 
| 68 | 
            +
                  error_response = lambda { |response|  false }
         | 
| 69 69 | 
             
                  response = FaradayMiddleware.new(app, open_circuit: error_response).call(env)
         | 
| 70 70 | 
             
                  assert_kind_of Faraday::Response, response
         | 
| 71 | 
            -
                  assert_equal response.status,  | 
| 71 | 
            +
                  assert_equal response.status, 500
         | 
| 72 72 | 
             
                  assert response.finished?
         | 
| 73 73 | 
             
                  refute response.success?
         | 
| 74 74 | 
             
                end
         | 
| 75 75 |  | 
| 76 76 | 
             
                def test_default_success_response
         | 
| 77 | 
            +
                  env = { url: "url" }
         | 
| 78 | 
            +
                  app = gimme
         | 
| 79 | 
            +
                  give(app).call(anything) { Faraday::Response.new(status: 500) }
         | 
| 80 | 
            +
                  response = FaradayMiddleware.new(app).call(env)
         | 
| 81 | 
            +
                  assert_kind_of Faraday::Response, response
         | 
| 82 | 
            +
                  assert_equal response.status, 503
         | 
| 83 | 
            +
                  assert response.finished?
         | 
| 84 | 
            +
                  refute response.success?
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def test_default_open_circuit_does_not_trip_on_400
         | 
| 77 88 | 
             
                  env = { url: "url" }
         | 
| 78 89 | 
             
                  app = gimme
         | 
| 79 90 | 
             
                  give(app).call(anything) { Faraday::Response.new(status: 400) }
         | 
| 80 91 | 
             
                  response = FaradayMiddleware.new(app).call(env)
         | 
| 81 92 | 
             
                  assert_kind_of Faraday::Response, response
         | 
| 93 | 
            +
                  assert_equal response.status, 400
         | 
| 94 | 
            +
                  assert response.finished?
         | 
| 95 | 
            +
                  refute response.success?
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def test_default_open_circuit_does_trip_on_nil
         | 
| 99 | 
            +
                  env = { url: "url" }
         | 
| 100 | 
            +
                  app = gimme
         | 
| 101 | 
            +
                  give(app).call(anything) { Faraday::Response.new(status: nil) }
         | 
| 102 | 
            +
                  response = FaradayMiddleware.new(app).call(env)
         | 
| 103 | 
            +
                  assert_kind_of Faraday::Response, response
         | 
| 82 104 | 
             
                  assert_equal response.status, 503
         | 
| 83 105 | 
             
                  assert response.finished?
         | 
| 84 106 | 
             
                  refute response.success?
         | 
| @@ -4,7 +4,6 @@ require "typhoeus/adapters/faraday" | |
| 4 4 | 
             
            require "pstore"
         | 
| 5 5 |  | 
| 6 6 | 
             
            class Circuitbox
         | 
| 7 | 
            -
             | 
| 8 7 | 
             
              class CrossProcessTest < Minitest::Test
         | 
| 9 8 | 
             
                include IntegrationHelpers
         | 
| 10 9 |  | 
| @@ -21,16 +20,17 @@ class Circuitbox | |
| 21 20 | 
             
                      circuit_breaker_options: { cache: Moneta.new(:PStore, file: dbfile) }
         | 
| 22 21 | 
             
                    c.adapter :typhoeus # support in_parallel
         | 
| 23 22 | 
             
                  end
         | 
| 24 | 
            -
                  @failure_url = "http:// | 
| 23 | 
            +
                  @failure_url = "http://127.0.0.1:4713"
         | 
| 25 24 |  | 
| 26 25 | 
             
                  if !@@only_once
         | 
| 27 | 
            -
                     | 
| 28 | 
            -
                      Rack::Handler::WEBrick.run( | 
| 26 | 
            +
                    pid = fork do
         | 
| 27 | 
            +
                      Rack::Handler::WEBrick.run(lambda { |env| [500, {}, ["Failure"]] },
         | 
| 29 28 | 
             
                                                 Port: 4713,
         | 
| 30 29 | 
             
                                                 AccessLog: [],
         | 
| 31 30 | 
             
                                                 Logger: WEBrick::Log.new(DEV_NULL))
         | 
| 32 31 | 
             
                    end
         | 
| 33 | 
            -
                     | 
| 32 | 
            +
                    sleep 0.5
         | 
| 33 | 
            +
                    Minitest.after_run { Process.kill "KILL", pid }
         | 
| 34 34 | 
             
                  end
         | 
| 35 35 | 
             
                end
         | 
| 36 36 |  | 
| @@ -44,6 +44,7 @@ class Circuitbox | |
| 44 44 | 
             
                    con = Faraday.new do |c|
         | 
| 45 45 | 
             
                      c.use FaradayMiddleware, identifier: "circuitbox_test_cross_process",
         | 
| 46 46 | 
             
                        circuit_breaker_options: { cache: Moneta.new(:PStore, file: dbfile) }
         | 
| 47 | 
            +
                      c.adapter :typhoeus
         | 
| 47 48 | 
             
                    end
         | 
| 48 49 | 
             
                    open_circuit(con)
         | 
| 49 50 | 
             
                  end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: circuitbox
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Fahim Ferdous
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2016- | 
| 11 | 
            +
            date: 2016-02-12 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         | 
| @@ -261,7 +261,6 @@ files: | |
| 261 261 | 
             
            - lib/circuitbox/notifier.rb
         | 
| 262 262 | 
             
            - lib/circuitbox/railtie.rb
         | 
| 263 263 | 
             
            - lib/circuitbox/version.rb
         | 
| 264 | 
            -
            - lib/tasks/circuits.rake
         | 
| 265 264 | 
             
            - test/circuit_breaker_test.rb
         | 
| 266 265 | 
             
            - test/circuitbox_test.rb
         | 
| 267 266 | 
             
            - test/excon_middleware_test.rb
         | 
    
        data/lib/tasks/circuits.rake
    DELETED
    
    | @@ -1,12 +0,0 @@ | |
| 1 | 
            -
            namespace :circuits do
         | 
| 2 | 
            -
              task :stats => :environment do
         | 
| 3 | 
            -
                service = ENV['SERVICE']
         | 
| 4 | 
            -
                partition_key = ENV['PARTITION']
         | 
| 5 | 
            -
             | 
| 6 | 
            -
                if service.blank?
         | 
| 7 | 
            -
                  raise "You must specify a SERVICE env variable, eg. `bundle exec rake circuits:stats SERVICE=yammer`"
         | 
| 8 | 
            -
                else
         | 
| 9 | 
            -
                  pp Circuitbox::CircuitBreaker.new(service).stats(partition_key)
         | 
| 10 | 
            -
                end
         | 
| 11 | 
            -
              end
         | 
| 12 | 
            -
            end
         |