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
|