circuitbox 0.11.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|