circuitbox 0.10.4 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5dd97fd3064e288e32ebae417b38a8e4e95439e4
4
- data.tar.gz: 0b5ede74d70a2bfeffaf8178304afb24e3ac1c61
3
+ metadata.gz: d0fe08dafcf12318b2880700034caf98461a2f3e
4
+ data.tar.gz: f136784e4d4d32ffa0b53580fb64eded3bf4d5ba
5
5
  SHA512:
6
- metadata.gz: 7b6dfa97e8ea99ddf7d3ff06c08460e7820a07461f8c4c9cedf84c012879ba72870ae9f032cc6047e1121024ba64329ae2f9c96929f05e90268569f6279a0c9c
7
- data.tar.gz: 9870a54f290f71a377ae850cc02945d6f76eeec7c5dc3627cf710e67adeb844cc14eaaf4341683508fb9c81e1e8d35f7fc51f841874dbf5163c1bca251b49cf7
6
+ metadata.gz: 0e2870b5f3b3cffd2623f04937be7794fe2ee0085655b65b2533da3ba421e9e7772c4b85593097f300aa4f0984b484f590a8811eb22b667a31e7a8d81c9070d2
7
+ data.tar.gz: c98b3b8e64b4def135717ae7eedb833807d4a484cb6772d284c38c08cb992013a8adc64041ffaddb529fdfecda187287160216467940b63c252a5d3ce765bcb7
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
+ .vagrant
2
3
  *.rbc
3
4
  .bundle
4
5
  .config
@@ -13,6 +14,7 @@ pkg
13
14
  rdoc
14
15
  spec/reports
15
16
  test/tmp
17
+ test/reports
16
18
  test/version_tmp
17
19
  tmp
18
20
  .idea
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.1.5
data/.travis.yml CHANGED
@@ -4,3 +4,5 @@ rvm:
4
4
  - 2.0.0
5
5
  - 2.1
6
6
  - 2.2
7
+ before_install:
8
+ - gem install bundler
data/Gemfile CHANGED
@@ -3,3 +3,4 @@ source 'https://rubygems.org'
3
3
  # Specify your gem's dependencies in circuitbox.gemspec
4
4
  gemspec
5
5
 
6
+ gem "pry"
data/README.md CHANGED
@@ -57,6 +57,10 @@ class ExampleServiceClient
57
57
  # number of requests within 1 minute before it calculates error rates
58
58
  volume_threshold: 10,
59
59
 
60
+ # the store you want to use to save the circuit state so it can be
61
+ # tracked, this needs to be Moneta compatible, and support increment
62
+ cache: Moneta.new(:Memory)
63
+
60
64
  # exceeding this rate will open the circuit
61
65
  error_threshold: 50,
62
66
 
@@ -75,6 +79,19 @@ Circuitbox.circuit(:yammer, {
75
79
  })
76
80
  ```
77
81
 
82
+ ## Circuit Store (:cache)
83
+
84
+ Holds all the relevant data to trip the circuit if a given number of requests
85
+ fail in a specified period of time. The store is based on
86
+ [Moneta](https://github.com/minad/moneta) so there are a lot of stores to choose
87
+ from. There are some pre-requisits they need to satisfy so:
88
+
89
+ - Need to support increment, this is true for most but not all available stores.
90
+ - Need to support concurrent access if you share them. For example sharing a
91
+ KyotoCabinet store across process fails because the store is single writer
92
+ multiple readers, and all circuits sharing the store need to be able to write.
93
+
94
+
78
95
  ## Monitoring & Statistics
79
96
 
80
97
  You can also run `rake circuits:stats SERVICE={service_name}` to see successes, failures and opened circuits.
@@ -136,6 +153,74 @@ end
136
153
 
137
154
  ```
138
155
 
156
+ ### Multi process Circuits
157
+
158
+ `circuit_store` is backed by [Moneta](https://github.com/minad/moneta) which
159
+ supports multiple backends. This can be configured by passing `cache:
160
+ Moneta.new(:PStore, file: "myfile.store")` to use for example the built in
161
+ PStore ruby library for persisted store, which can be shared cross process.
162
+
163
+ Depending on your requirements different stores can make sense, see the
164
+ benchmarks and [moneta
165
+ feature](https://github.com/minad/moneta#backend-feature-matrix) matrix for
166
+ details.
167
+
168
+ ```
169
+ user system total real
170
+ memory: 1.440000 0.140000 1.580000 ( 1.579244)
171
+ lmdb: 4.330000 3.280000 7.610000 ( 13.086398)
172
+ pstore: 23.680000 4.350000 28.030000 ( 28.094312)
173
+ daybreak: 2.270000 0.450000 2.720000 ( 2.626748)
174
+ ```
175
+
176
+ You can run the benchmarks yourself by running `rake benchmark`.
177
+
178
+ ### Memory
179
+
180
+ An in memory store, which is local to the process. This is not threadsafe so it
181
+ is not useable with multithreaded webservers for example. It is always going to
182
+ be the fastest option if no multi-process or thread is required, like in
183
+ development on Webbrick.
184
+
185
+ This is the default.
186
+
187
+ ```ruby
188
+ Circuitbox.circuit :identifier, cache: Moneta.new(:Memory)
189
+ ```
190
+
191
+ ### LMDB
192
+
193
+ An persisted directory backed store, which is thread and multi process save.
194
+ depends on the `lmdb` gem. It is slower than Memory or Daybreak, but can be
195
+ used in multi thread and multi process environments like like Puma.
196
+
197
+ ```ruby
198
+ require "lmdb"
199
+ Circuitbox.circuit :identifier, cache: Moneta.new(:LMDB, dir: "./", db: "mydb")
200
+ ```
201
+
202
+ ### PStore
203
+
204
+ An persisted file backed store, which comes with the ruby
205
+ [stdlib](http://ruby-doc.org/stdlib-2.3.0/libdoc/pstore/rdoc/PStore.html). It
206
+ has no external dependecies and works on every ruby implementation. Due to it
207
+ being file backed it is multi process save, good for development using Unicorn.
208
+
209
+ ```ruby
210
+ Circuitbox.circuit :identifier, cache: Moneta.new(:PStore, file: "db.pstore")
211
+ ```
212
+
213
+ ### Daybreak
214
+
215
+ Persisted, file backed key value store in pure ruby. It is process save and
216
+ outperforms most other stores in circuitbox. This is recommended for production
217
+ use with Unicorn. It depends on the `daybreak` gem.
218
+
219
+ ```ruby
220
+ require "daybreak"
221
+ Circuitbox.circuit :identifier, cache: Moneta.new(:Daybreak, file: "db.daybreak")
222
+ ```
223
+
139
224
  ## Faraday
140
225
 
141
226
  Circuitbox ships with [Faraday HTTP client](https://github.com/lostisland/faraday) middleware.
@@ -212,6 +297,8 @@ c.use Circuitbox::FaradayMiddleware, open_circuit: lambda { |response| response.
212
297
  ## CHANGELOG
213
298
 
214
299
  ### version next
300
+ - fix URI require missing (https://github.com/yammer/circuitbox/pull/42 @gottfrois)
301
+ - configurable circuitbox store backend via Moneta supporting multi process circuits
215
302
 
216
303
  ### v0.10.4
217
304
  - Issue #39, keep the original backtrace for the wrapped exception around when
data/Rakefile CHANGED
@@ -6,5 +6,13 @@ Rake::TestTask.new do |t|
6
6
  t.test_files = FileList['test/**/*_test.rb']
7
7
  end
8
8
 
9
+ desc "run the circuitbox benchmark scripts"
10
+ task :benchmark do
11
+ benchmark_scripts = FileList.new("./benchmark/*_benchmark.rb")
12
+ benchmark_scripts.each do |script|
13
+ system "bundle exec ruby #{script}"
14
+ end
15
+ end
16
+
9
17
  desc "Run tests"
10
18
  task :default => :test
@@ -0,0 +1,116 @@
1
+ require 'circuitbox'
2
+ require 'benchmark'
3
+ require 'pstore'
4
+ require 'tempfile'
5
+ require 'tmpdir'
6
+ require 'lmdb'
7
+ require 'pry'
8
+
9
+
10
+ class Circuitbox
11
+ class CircuitBreaker
12
+ # silence the circuitbreaker logger
13
+ DEV_NULL = (RUBY_PLATFORM =~ /mswin|mingw/ ? "NUL" : "/dev/null")
14
+ def logger
15
+ @_dev_null_logger ||= Logger.new DEV_NULL
16
+ end
17
+ end
18
+ end
19
+
20
+ def service
21
+ # 10% success rate to make the circuitbreaker flip flop
22
+ if rand(10) <= 0
23
+ "success"
24
+ else
25
+ raise RuntimeError, "fail"
26
+ end
27
+ end
28
+
29
+ def run_flip_flopping circuit
30
+ before = circuit.open?
31
+ circuit.run { service }
32
+ after = circuit.open?
33
+ circuit.try_close_next_time if circuit.open?
34
+ end
35
+
36
+ def without_gc
37
+ GC.start
38
+ GC.disable
39
+ yield
40
+ GC.enable
41
+ end
42
+
43
+ def benchmark_circuitbox_method_with_reporter method, reporter
44
+ without_gc { send(method, reporter) }
45
+ Circuitbox.reset
46
+ end
47
+
48
+ def circuit_with_cache cache
49
+ Circuitbox.circuit :performance, CIRCUIT_OPTIONS.merge(cache: cache)
50
+ end
51
+
52
+ CIRCUIT_OPTIONS = {
53
+ exceptions: [RuntimeError],
54
+ sleep_window: 0,
55
+ time_window: 1
56
+ }
57
+
58
+ RUNS = 10000
59
+
60
+ def circuit_store_memory_one_process reporter
61
+ circuit = circuit_with_cache Moneta.new(:Memory)
62
+
63
+ reporter.report "memory:" do
64
+ RUNS.times { run_flip_flopping circuit }
65
+ end
66
+
67
+ circuit.circuit_store.close
68
+ end
69
+
70
+ def circuit_store_pstore_one_process reporter
71
+ Tempfile.create("test_circuit_store_pstore_one_process") do |dbfile|
72
+ circuit = circuit_with_cache Moneta.new(:PStore, file: dbfile)
73
+
74
+ reporter.report "pstore:" do
75
+ RUNS.times { run_flip_flopping circuit }
76
+ end
77
+
78
+ circuit.circuit_store.close
79
+ end
80
+ end
81
+
82
+ def circuit_store_lmdb_one_process reporter
83
+ Dir.mktmpdir("test_circuit_store_lmdb_one_process") do |dbdir|
84
+ circuit = circuit_with_cache Moneta.new(:LMDB, dir: dbdir, db: "circuitbox_lmdb")
85
+
86
+ reporter.report "lmdb:" do
87
+ RUNS.times { run_flip_flopping circuit }
88
+ end
89
+
90
+ circuit.circuit_store.close
91
+ end
92
+ end
93
+
94
+ def circuit_store_daybreak_one_process reporter
95
+ Tempfile.create("test_circuit_store_daybreak_one_process") do |dbfile|
96
+ circuit = circuit_with_cache Moneta.new(:Daybreak, file: dbfile)
97
+
98
+ reporter.report "daybreak:" do
99
+ RUNS.times { run_flip_flopping circuit }
100
+ end
101
+
102
+ circuit.circuit_store.close
103
+ end
104
+ end
105
+
106
+ Benchmark.bm(8) do |x|
107
+ benchmark_circuitbox_method_with_reporter :circuit_store_memory_one_process, x
108
+ benchmark_circuitbox_method_with_reporter :circuit_store_lmdb_one_process, x
109
+ benchmark_circuitbox_method_with_reporter :circuit_store_pstore_one_process, x
110
+ benchmark_circuitbox_method_with_reporter :circuit_store_daybreak_one_process, x
111
+ end
112
+
113
+
114
+
115
+
116
+
data/circuitbox.gemspec CHANGED
@@ -27,8 +27,12 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency "typhoeus"
28
28
  spec.add_development_dependency "timecop"
29
29
  spec.add_development_dependency "faraday"
30
+ spec.add_development_dependency "excon"
30
31
  spec.add_development_dependency "logger"
31
32
  spec.add_development_dependency "bundler-gem_version_tasks"
33
+ spec.add_development_dependency "lmdb"
34
+ spec.add_development_dependency "daybreak"
32
35
 
33
36
  spec.add_dependency "activesupport"
37
+ spec.add_dependency "moneta"
34
38
  end
@@ -118,11 +118,11 @@ class Circuitbox
118
118
  end
119
119
 
120
120
  def failure_count
121
- circuit_store.read(stat_storage_key(:failure)).to_i
121
+ circuit_store.load(stat_storage_key(:failure), raw: true).to_i
122
122
  end
123
123
 
124
124
  def success_count
125
- circuit_store.read(stat_storage_key(:success)).to_i
125
+ circuit_store.load(stat_storage_key(:success), raw: true).to_i
126
126
  end
127
127
 
128
128
  def try_close_next_time
@@ -133,7 +133,7 @@ class Circuitbox
133
133
  def open!
134
134
  log_event :open
135
135
  logger.debug "[CIRCUIT] opening #{service} circuit"
136
- circuit_store.write(storage_key(:asleep), true, expires_in: option_value(:sleep_window).seconds)
136
+ circuit_store.store(storage_key(:asleep), true, expires_in: option_value(:sleep_window).seconds)
137
137
  half_open!
138
138
  was_open!
139
139
  end
@@ -145,24 +145,24 @@ class Circuitbox
145
145
  end
146
146
 
147
147
  def was_open!
148
- circuit_store.write(storage_key(:was_open), true)
148
+ circuit_store.store(storage_key(:was_open), true)
149
149
  end
150
150
 
151
151
  def was_open?
152
- circuit_store.read(storage_key(:was_open)).present?
152
+ circuit_store[storage_key(:was_open)].present?
153
153
  end
154
154
  ### END
155
155
 
156
156
  def half_open!
157
- circuit_store.write(storage_key(:half_open), true)
157
+ circuit_store.store(storage_key(:half_open), true)
158
158
  end
159
159
 
160
160
  def open_flag?
161
- circuit_store.read(storage_key(:asleep)).present?
161
+ circuit_store[storage_key(:asleep)].present?
162
162
  end
163
163
 
164
164
  def half_open?
165
- circuit_store.read(storage_key(:half_open)).present?
165
+ circuit_store[storage_key(:half_open)].present?
166
166
  end
167
167
 
168
168
  def passed_volume_threshold?
@@ -219,16 +219,17 @@ class Circuitbox
219
219
 
220
220
  # When there is a successful response within a stat interval, clear the failures.
221
221
  def clear_failures!
222
- circuit_store.write(stat_storage_key(:failure), 0, raw: true)
222
+ circuit_store.store(stat_storage_key(:failure), 0, raw: true)
223
223
  end
224
224
 
225
225
  # Logs to process memory.
226
226
  def log_event_to_process(event)
227
227
  key = stat_storage_key(event)
228
- if circuit_store.read(key, raw: true)
228
+ if circuit_store.load(key, raw: true)
229
229
  circuit_store.increment(key)
230
230
  else
231
- circuit_store.write(key, 1, raw: true)
231
+ # yes we want a string here, as the underlying stores impement this as a native type.
232
+ circuit_store.store(key, "1", raw: true)
232
233
  end
233
234
  end
234
235
 
@@ -237,7 +238,7 @@ class Circuitbox
237
238
  if stat_store.read(key, raw: true)
238
239
  stat_store.increment(key)
239
240
  else
240
- stat_store.write(key, 1, raw: true)
241
+ stat_store.store(key, 1)
241
242
  end
242
243
  end
243
244
 
@@ -0,0 +1,111 @@
1
+ require 'excon'
2
+ require 'circuitbox'
3
+
4
+ class Circuitbox
5
+ class ExconMiddleware < Excon::Middleware::Base
6
+ class RequestFailed < StandardError; end
7
+
8
+ DEFAULT_EXCEPTIONS = [
9
+ Excon::Errors::Timeout,
10
+ RequestFailed
11
+ ]
12
+
13
+ class NullResponse < Excon::Response
14
+ def initialize(response, exception)
15
+ @original_response = response
16
+ @original_exception = exception
17
+ super(status: 503, response_headers: {})
18
+ end
19
+
20
+ def []=(key, value)
21
+ @data[key] = value
22
+ end
23
+ end
24
+
25
+ attr_reader :opts
26
+
27
+ def initialize(stack, opts = {})
28
+ @stack = stack
29
+ default_options = { open_circuit: lambda { |response| response[:status] >= 400 } }
30
+ @opts = default_options.merge(opts)
31
+ super(stack)
32
+ end
33
+
34
+ def error_call(datum)
35
+ circuit(datum).run!(run_options(datum)) do
36
+ raise RequestFailed
37
+ end
38
+ rescue Circuitbox::Error => exception
39
+ circuit_open_value(datum, datum[:response], exception)
40
+ end
41
+
42
+ def request_call(datum)
43
+ circuit(datum).run!(run_options(datum)) do
44
+ @stack.request_call(datum)
45
+ end
46
+ end
47
+
48
+ def response_call(datum)
49
+ circuit(datum).run!(run_options(datum)) do
50
+ raise RequestFailed if open_circuit?(datum[:response])
51
+ end
52
+ @stack.response_call(datum)
53
+ rescue Circuitbox::Error => exception
54
+ circuit_open_value(datum, datum[:response], exception)
55
+ end
56
+
57
+ def identifier
58
+ @identifier ||= opts.fetch(:identifier, ->(env) { env[:path] })
59
+ end
60
+
61
+ def exceptions
62
+ circuit_breaker_options[:exceptions]
63
+ end
64
+
65
+ private
66
+
67
+ def circuit(datum)
68
+ id = identifier.respond_to?(:call) ? identifier.call(datum) : identifier
69
+ circuitbox.circuit id, circuit_breaker_options
70
+ end
71
+
72
+ def run_options(datum)
73
+ opts.merge(datum)[:circuit_breaker_run_options] || {}
74
+ end
75
+
76
+ def open_circuit?(response)
77
+ opts[:open_circuit].call(response)
78
+ end
79
+
80
+ def circuitbox
81
+ @circuitbox ||= opts.fetch(:circuitbox, Circuitbox)
82
+ end
83
+
84
+ def circuit_open_value(env, response, exception)
85
+ env[:circuit_breaker_default_value] || default_value.call(response, exception)
86
+ end
87
+
88
+ def circuit_breaker_options
89
+ return @circuit_breaker_options if @current_adapter
90
+
91
+ @circuit_breaker_options = opts.fetch(:circuit_breaker_options, {})
92
+ @circuit_breaker_options.merge!(
93
+ exceptions: opts.fetch(:exceptions, DEFAULT_EXCEPTIONS)
94
+ )
95
+ end
96
+
97
+ def default_value
98
+ return @default_value if @default_value
99
+
100
+ default = opts.fetch(:default_value) do
101
+ lambda { |response, exception| NullResponse.new(response, exception) }
102
+ end
103
+
104
+ @default_value = if default.respond_to?(:call)
105
+ default
106
+ else
107
+ lambda { |*| default }
108
+ end
109
+ end
110
+ end
111
+ end
@@ -1,3 +1,3 @@
1
1
  class Circuitbox
2
- VERSION='0.10.4'
2
+ VERSION='0.11.0'
3
3
  end
data/lib/circuitbox.rb CHANGED
@@ -1,11 +1,13 @@
1
+ require 'uri'
1
2
  require 'singleton'
2
3
  require 'active_support'
3
4
  require 'logger'
4
5
  require 'timeout'
6
+ require 'moneta'
5
7
 
6
8
  require 'circuitbox/version'
7
9
  require 'circuitbox/memcache_store'
8
- require 'circuitbox/railtie' if defined?(Rails)
10
+ require 'circuitbox/railtie' if defined?(Rails)
9
11
  require 'circuitbox/circuit_breaker'
10
12
  require 'circuitbox/notifier'
11
13
 
@@ -35,7 +37,7 @@ class Circuitbox
35
37
  end
36
38
 
37
39
  def self.circuit_store
38
- self.instance.circuit_store ||= ActiveSupport::Cache::MemoryStore.new
40
+ self.instance.circuit_store ||= Moneta.new(:Memory)
39
41
  end
40
42
 
41
43
  def self.circuit_store=(store)
@@ -0,0 +1,131 @@
1
+ require 'test_helper'
2
+ require 'circuitbox/excon_middleware'
3
+
4
+ class SentialException < StandardError; end
5
+
6
+ class Circuitbox
7
+ class ExconMiddlewareTest < Minitest::Test
8
+
9
+ attr_reader :app
10
+
11
+ def setup
12
+ @app = gimme
13
+ end
14
+
15
+ def test_default_identifier
16
+ env = { path: "sential" }
17
+ assert_equal "sential", ExconMiddleware.new(app).identifier.call(env)
18
+ end
19
+
20
+ def test_overwrite_identifier
21
+ middleware = ExconMiddleware.new(app, identifier: "sential")
22
+ assert_equal middleware.identifier, "sential"
23
+ end
24
+
25
+ def test_overwrite_default_value_generator_lambda
26
+ stub_circuitbox
27
+ env = { path: "path" }
28
+ give(circuitbox).circuit("path", anything) { circuit }
29
+ give(circuit).run!(anything) { raise Circuitbox::Error }
30
+ default_value_generator = lambda { |_, _| :sential }
31
+ middleware = ExconMiddleware.new(app,
32
+ circuitbox: circuitbox,
33
+ default_value: default_value_generator)
34
+ assert_equal :sential, middleware.error_call(env)
35
+ end
36
+
37
+ def test_overwrite_default_value_generator_static_value
38
+ stub_circuitbox
39
+ env = { path: "path" }
40
+ give(circuitbox).circuit("path", anything) { circuit }
41
+ give(circuit).run!(anything) { raise Circuitbox::Error }
42
+ middleware = ExconMiddleware.new(app, circuitbox: circuitbox, default_value: :sential)
43
+ assert_equal :sential, middleware.error_call(env)
44
+ end
45
+
46
+ def test_default_exceptions
47
+ middleware = ExconMiddleware.new(app)
48
+ assert_includes middleware.exceptions, Excon::Errors::Timeout
49
+ assert_includes middleware.exceptions, ExconMiddleware::RequestFailed
50
+ end
51
+
52
+ def test_overridde_success_response
53
+ env = { path: "path", response: { status: 400 } }
54
+ error_response = lambda { |r| r[:status] >= 500 }
55
+ give(app).response_call(anything) { Excon::Response.new(status: 400) }
56
+ mw = ExconMiddleware.new(app, open_circuit: error_response)
57
+ response = mw.response_call(env)
58
+ assert_kind_of Excon::Response, response
59
+ assert_equal response.status, 400
60
+ end
61
+
62
+ def test_default_success_response
63
+ env = { path: "path", response: { status: 400 } }
64
+ app = gimme
65
+ give(app).response_call(anything) { Excon::Response.new(status: 400) }
66
+ response = nil
67
+
68
+ mw = ExconMiddleware.new(app)
69
+ response = mw.response_call(env)
70
+
71
+ assert_kind_of Excon::Response, response
72
+ assert_equal response.status, 503
73
+ end
74
+
75
+ def test_overwrite_exceptions
76
+ middleware = ExconMiddleware.new(app, exceptions: [SentialException])
77
+ assert_includes middleware.exceptions, SentialException
78
+ end
79
+
80
+ def test_pass_circuit_breaker_run_options
81
+ stub_circuitbox
82
+ give(circuit).run!(:sential)
83
+ give(circuitbox).circuit("path", anything) { circuit }
84
+ env = { path: "path", circuit_breaker_run_options: :sential }
85
+ middleware = ExconMiddleware.new(app, circuitbox: circuitbox)
86
+ middleware.request_call(env)
87
+ verify(circuit, 1.times).run!(:sential)
88
+ end
89
+
90
+ def test_pass_circuit_breaker_options
91
+ stub_circuitbox
92
+ env = { path: "path" }
93
+ expected_circuit_breaker_options = {
94
+ sential: :sential,
95
+ exceptions: ExconMiddleware::DEFAULT_EXCEPTIONS
96
+ }
97
+ give(circuitbox).circuit("path", expected_circuit_breaker_options) { circuit }
98
+ options = { circuitbox: circuitbox, circuit_breaker_options: { sential: :sential } }
99
+ middleware = ExconMiddleware.new(app, options)
100
+ middleware.request_call(env)
101
+
102
+ verify(circuitbox, 1.times).circuit("path", expected_circuit_breaker_options)
103
+ end
104
+
105
+ def test_overwrite_circuitbreaker_default_value
106
+ stub_circuitbox
107
+ env = { path: "path", circuit_breaker_default_value: :sential }
108
+ give(circuitbox).circuit("path", anything) { circuit }
109
+ give(circuit).run!(anything) { raise Circuitbox::Error }
110
+ middleware = ExconMiddleware.new(app, circuitbox: circuitbox)
111
+ assert_equal middleware.error_call(env), :sential
112
+ end
113
+
114
+ def test_return_null_response_for_open_circuit
115
+ stub_circuitbox
116
+ env = { path: "path" }
117
+ give(circuit).run!(anything) { raise Circuitbox::Error }
118
+ give(circuitbox).circuit("path", anything) { circuit }
119
+ mw = ExconMiddleware.new(app, circuitbox: circuitbox)
120
+ response = mw.error_call(env)
121
+ assert_kind_of Excon::Response, response
122
+ assert_equal response.status, 503
123
+ end
124
+
125
+ attr_reader :circuitbox, :circuit
126
+ def stub_circuitbox
127
+ @circuitbox = gimme
128
+ @circuit = gimme
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,55 @@
1
+ require "integration_helper"
2
+ require "tempfile"
3
+ require "typhoeus/adapters/faraday"
4
+ require "pstore"
5
+
6
+ class Circuitbox
7
+
8
+ class CrossProcessTest < Minitest::Test
9
+ include IntegrationHelpers
10
+
11
+ attr_reader :connection, :failure_url, :dbfile
12
+
13
+ @@only_once = false
14
+ def setup
15
+ if !@@only_once
16
+ @dbfile = Tempfile.open("circuitbox_test_cross_process")
17
+ end
18
+
19
+ @connection = Faraday.new do |c|
20
+ c.use FaradayMiddleware, identifier: "circuitbox_test_cross_process",
21
+ circuit_breaker_options: { cache: Moneta.new(:PStore, file: dbfile) }
22
+ c.adapter :typhoeus # support in_parallel
23
+ end
24
+ @failure_url = "http://localhost:4713"
25
+
26
+ if !@@only_once
27
+ thread = Thread.new do
28
+ Rack::Handler::WEBrick.run(Proc.new { |env| ["Failure"] },
29
+ Port: 4713,
30
+ AccessLog: [],
31
+ Logger: WEBrick::Log.new(DEV_NULL))
32
+ end
33
+ Minitest.after_run { thread.exit }
34
+ end
35
+ end
36
+
37
+ def teardown
38
+ Circuitbox.reset
39
+ end
40
+
41
+ def test_circuit_opens_cross_process
42
+ # Open the circuit via a different process
43
+ pid = fork do
44
+ con = Faraday.new do |c|
45
+ c.use FaradayMiddleware, identifier: "circuitbox_test_cross_process",
46
+ circuit_breaker_options: { cache: Moneta.new(:PStore, file: dbfile) }
47
+ end
48
+ open_circuit(con)
49
+ end
50
+ Process.wait pid
51
+ response = connection.get(failure_url)
52
+ assert response.original_response.nil?, "opening the circuit from a different process should be respected in the main process"
53
+ end
54
+ end
55
+ end
@@ -40,9 +40,9 @@ class FakeServer
40
40
  end
41
41
 
42
42
  module IntegrationHelpers
43
- def open_circuit
44
- volume_threshold = Circuitbox['test'].option_value(:volume_threshold)
45
- (volume_threshold + 1).times { connection.get(failure_url) }
43
+ def open_circuit(c = connection)
44
+ volume_threshold = Circuitbox::CircuitBreaker::DEFAULTS[:volume_threshold]
45
+ (volume_threshold + 1).times { c.get(failure_url) }
46
46
  end
47
47
  end
48
48
 
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.10.4
4
+ version: 0.11.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: 2015-08-03 00:00:00.000000000 Z
11
+ date: 2016-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -136,6 +136,20 @@ dependencies:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: excon
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
139
153
  - !ruby/object:Gem::Dependency
140
154
  name: logger
141
155
  requirement: !ruby/object:Gem::Requirement
@@ -164,6 +178,34 @@ dependencies:
164
178
  - - ">="
165
179
  - !ruby/object:Gem::Version
166
180
  version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: lmdb
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: daybreak
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
167
209
  - !ruby/object:Gem::Dependency
168
210
  name: activesupport
169
211
  requirement: !ruby/object:Gem::Requirement
@@ -178,6 +220,20 @@ dependencies:
178
220
  - - ">="
179
221
  - !ruby/object:Gem::Version
180
222
  version: '0'
223
+ - !ruby/object:Gem::Dependency
224
+ name: moneta
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ">="
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ type: :runtime
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ">="
235
+ - !ruby/object:Gem::Version
236
+ version: '0'
181
237
  description: A robust circuit breaker that manages failing external services.
182
238
  email:
183
239
  - fahimfmf@gmail.com
@@ -186,17 +242,20 @@ extensions: []
186
242
  extra_rdoc_files: []
187
243
  files:
188
244
  - ".gitignore"
245
+ - ".ruby-version"
189
246
  - ".travis.yml"
190
247
  - Gemfile
191
248
  - LICENSE
192
249
  - README.md
193
250
  - Rakefile
251
+ - benchmark/circuit_store_benchmark.rb
194
252
  - circuitbox.gemspec
195
253
  - lib/circuitbox.rb
196
254
  - lib/circuitbox/circuit_breaker.rb
197
255
  - lib/circuitbox/errors/error.rb
198
256
  - lib/circuitbox/errors/open_circuit_error.rb
199
257
  - lib/circuitbox/errors/service_failure_error.rb
258
+ - lib/circuitbox/excon_middleware.rb
200
259
  - lib/circuitbox/faraday_middleware.rb
201
260
  - lib/circuitbox/memcache_store.rb
202
261
  - lib/circuitbox/notifier.rb
@@ -205,7 +264,9 @@ files:
205
264
  - lib/tasks/circuits.rake
206
265
  - test/circuit_breaker_test.rb
207
266
  - test/circuitbox_test.rb
267
+ - test/excon_middleware_test.rb
208
268
  - test/faraday_middleware_test.rb
269
+ - test/integration/circuitbox_cross_process_open_test.rb
209
270
  - test/integration/faraday_middleware_test.rb
210
271
  - test/integration_helper.rb
211
272
  - test/notifier_test.rb
@@ -238,7 +299,9 @@ summary: A robust circuit breaker that manages failing external services.
238
299
  test_files:
239
300
  - test/circuit_breaker_test.rb
240
301
  - test/circuitbox_test.rb
302
+ - test/excon_middleware_test.rb
241
303
  - test/faraday_middleware_test.rb
304
+ - test/integration/circuitbox_cross_process_open_test.rb
242
305
  - test/integration/faraday_middleware_test.rb
243
306
  - test/integration_helper.rb
244
307
  - test/notifier_test.rb