circuitbox 1.1.1 → 2.0.0.pre4
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 +5 -5
- data/README.md +53 -187
- data/lib/circuitbox/circuit_breaker/logger_messages.rb +31 -0
- data/lib/circuitbox/circuit_breaker.rb +134 -154
- data/lib/circuitbox/configuration.rb +51 -0
- data/lib/circuitbox/errors/error.rb +3 -2
- data/lib/circuitbox/errors/open_circuit_error.rb +3 -1
- data/lib/circuitbox/errors/service_failure_error.rb +5 -1
- data/lib/circuitbox/excon_middleware.rb +23 -30
- data/lib/circuitbox/faraday_middleware.rb +43 -63
- data/lib/circuitbox/memory_store/container.rb +30 -0
- data/lib/circuitbox/memory_store/monotonic_time.rb +13 -0
- data/lib/circuitbox/memory_store.rb +85 -0
- data/lib/circuitbox/notifier/active_support.rb +19 -0
- data/lib/circuitbox/notifier/null.rb +13 -0
- data/lib/circuitbox/timer.rb +51 -0
- data/lib/circuitbox/version.rb +3 -1
- data/lib/circuitbox.rb +14 -54
- metadata +106 -117
- data/.gitignore +0 -20
- data/.ruby-version +0 -1
- data/.travis.yml +0 -9
- data/Gemfile +0 -6
- data/Rakefile +0 -30
- data/benchmark/circuit_store_benchmark.rb +0 -114
- data/circuitbox.gemspec +0 -48
- data/lib/circuitbox/notifier.rb +0 -34
- data/test/circuit_breaker_test.rb +0 -436
- data/test/circuitbox_test.rb +0 -45
- data/test/excon_middleware_test.rb +0 -131
- data/test/faraday_middleware_test.rb +0 -175
- data/test/integration/circuitbox_cross_process_open_test.rb +0 -56
- data/test/integration/faraday_middleware_test.rb +0 -78
- data/test/integration_helper.rb +0 -48
- data/test/notifier_test.rb +0 -21
- data/test/service_failure_error_test.rb +0 -23
- data/test/test_helper.rb +0 -15
@@ -1,175 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'circuitbox/faraday_middleware'
|
3
|
-
|
4
|
-
class SentialException < StandardError; end
|
5
|
-
|
6
|
-
class Circuitbox
|
7
|
-
class FaradayMiddlewareTest < Minitest::Test
|
8
|
-
|
9
|
-
attr_reader :app
|
10
|
-
|
11
|
-
def setup
|
12
|
-
@app = gimme
|
13
|
-
end
|
14
|
-
|
15
|
-
def test_default_identifier
|
16
|
-
env = { url: "sential" }
|
17
|
-
assert_equal "sential", FaradayMiddleware.new(app).identifier.call(env)
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_overwrite_identifier
|
21
|
-
middleware = FaradayMiddleware.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 = { url: "url" }
|
28
|
-
give(circuitbox).circuit("url", anything) { circuit }
|
29
|
-
give(circuit).run!(anything) { raise Circuitbox::Error }
|
30
|
-
default_value_generator = lambda { |response| :sential }
|
31
|
-
middleware = FaradayMiddleware.new(app,
|
32
|
-
circuitbox: circuitbox,
|
33
|
-
default_value: default_value_generator)
|
34
|
-
assert_equal :sential, middleware.call(env)
|
35
|
-
end
|
36
|
-
|
37
|
-
def test_default_value_generator_lambda_passed_error
|
38
|
-
stub_circuitbox
|
39
|
-
env = { url: "url" }
|
40
|
-
give(circuitbox).circuit("url", anything) { circuit }
|
41
|
-
give(circuit).run!(anything) { raise Circuitbox::Error.new("error text") }
|
42
|
-
default_value_generator = lambda { |_,error| error.message }
|
43
|
-
middleware = FaradayMiddleware.new(app,
|
44
|
-
circuitbox: circuitbox,
|
45
|
-
default_value: default_value_generator)
|
46
|
-
assert_equal "error text", middleware.call(env)
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_overwrite_default_value_generator_static_value
|
50
|
-
stub_circuitbox
|
51
|
-
env = { url: "url" }
|
52
|
-
give(circuitbox).circuit("url", anything) { circuit }
|
53
|
-
give(circuit).run!(anything) { raise Circuitbox::Error }
|
54
|
-
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox, default_value: :sential)
|
55
|
-
assert_equal :sential, middleware.call(env)
|
56
|
-
end
|
57
|
-
|
58
|
-
def test_default_exceptions
|
59
|
-
middleware = FaradayMiddleware.new(app)
|
60
|
-
assert_includes middleware.exceptions, Faraday::Error::TimeoutError
|
61
|
-
assert_includes middleware.exceptions, FaradayMiddleware::RequestFailed
|
62
|
-
end
|
63
|
-
|
64
|
-
def test_overridde_success_response
|
65
|
-
env = { url: "url" }
|
66
|
-
app = gimme
|
67
|
-
give(app).call(anything) { Faraday::Response.new(status: 500) }
|
68
|
-
error_response = lambda { |response| false }
|
69
|
-
response = FaradayMiddleware.new(app, open_circuit: error_response).call(env)
|
70
|
-
assert_kind_of Faraday::Response, response
|
71
|
-
assert_equal response.status, 500
|
72
|
-
assert response.finished?
|
73
|
-
refute response.success?
|
74
|
-
end
|
75
|
-
|
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
|
88
|
-
env = { url: "url" }
|
89
|
-
app = gimme
|
90
|
-
give(app).call(anything) { Faraday::Response.new(status: 400) }
|
91
|
-
response = FaradayMiddleware.new(app).call(env)
|
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
|
104
|
-
assert_equal response.status, 503
|
105
|
-
assert response.finished?
|
106
|
-
refute response.success?
|
107
|
-
end
|
108
|
-
|
109
|
-
def test_overwrite_exceptions
|
110
|
-
middleware = FaradayMiddleware.new(app, exceptions: [SentialException])
|
111
|
-
assert_includes middleware.exceptions, SentialException
|
112
|
-
end
|
113
|
-
|
114
|
-
def test_pass_circuit_breaker_run_options
|
115
|
-
stub_circuitbox
|
116
|
-
give(circuit).run!(:sential)
|
117
|
-
give(circuitbox).circuit("url", anything) { circuit }
|
118
|
-
env = { url: "url", circuit_breaker_run_options: :sential }
|
119
|
-
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox)
|
120
|
-
middleware.call(env)
|
121
|
-
verify(circuit, 1.times).run!(:sential)
|
122
|
-
end
|
123
|
-
|
124
|
-
def test_pass_circuit_breaker_options
|
125
|
-
stub_circuitbox
|
126
|
-
env = { url: "url" }
|
127
|
-
expected_circuit_breaker_options = {
|
128
|
-
sential: :sential,
|
129
|
-
exceptions: FaradayMiddleware::DEFAULT_EXCEPTIONS
|
130
|
-
}
|
131
|
-
give(circuitbox).circuit("url", expected_circuit_breaker_options) { circuit }
|
132
|
-
options = { circuitbox: circuitbox, circuit_breaker_options: { sential: :sential } }
|
133
|
-
middleware = FaradayMiddleware.new(app, options)
|
134
|
-
middleware.call(env)
|
135
|
-
|
136
|
-
verify(circuitbox, 1.times).circuit("url", expected_circuit_breaker_options)
|
137
|
-
end
|
138
|
-
|
139
|
-
def test_overwrite_circuitbreaker_default_value
|
140
|
-
stub_circuitbox
|
141
|
-
env = { url: "url", circuit_breaker_default_value: :sential }
|
142
|
-
give(circuitbox).circuit("url", anything) { circuit }
|
143
|
-
give(circuit).run!(anything) { raise Circuitbox::Error }
|
144
|
-
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox)
|
145
|
-
assert_equal middleware.call(env), :sential
|
146
|
-
end
|
147
|
-
|
148
|
-
def test_return_value_closed_circuit
|
149
|
-
stub_circuitbox
|
150
|
-
env = { url: "url" }
|
151
|
-
give(circuit).run!(anything) { :sential }
|
152
|
-
give(circuitbox).circuit("url", anything) { circuit }
|
153
|
-
middleware = FaradayMiddleware.new(app, circuitbox: circuitbox)
|
154
|
-
assert_equal middleware.call(env), :sential
|
155
|
-
end
|
156
|
-
|
157
|
-
def test_return_null_response_for_open_circuit
|
158
|
-
stub_circuitbox
|
159
|
-
env = { url: "url" }
|
160
|
-
give(circuit).run!(anything) { raise Circuitbox::Error }
|
161
|
-
give(circuitbox).circuit("url", anything) { circuit }
|
162
|
-
response = FaradayMiddleware.new(app, circuitbox: circuitbox).call(env)
|
163
|
-
assert_kind_of Faraday::Response, response
|
164
|
-
assert_equal response.status, 503
|
165
|
-
assert response.finished?
|
166
|
-
refute response.success?
|
167
|
-
end
|
168
|
-
|
169
|
-
attr_reader :circuitbox, :circuit
|
170
|
-
def stub_circuitbox
|
171
|
-
@circuitbox = gimme
|
172
|
-
@circuit = gimme
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require "integration_helper"
|
2
|
-
require "tempfile"
|
3
|
-
require "typhoeus/adapters/faraday"
|
4
|
-
require "pstore"
|
5
|
-
|
6
|
-
class Circuitbox
|
7
|
-
class CrossProcessTest < Minitest::Test
|
8
|
-
include IntegrationHelpers
|
9
|
-
|
10
|
-
attr_reader :connection, :failure_url, :dbfile
|
11
|
-
|
12
|
-
@@only_once = false
|
13
|
-
def setup
|
14
|
-
if !@@only_once
|
15
|
-
@dbfile = Tempfile.open("circuitbox_test_cross_process")
|
16
|
-
end
|
17
|
-
|
18
|
-
@connection = Faraday.new do |c|
|
19
|
-
c.use FaradayMiddleware, identifier: "circuitbox_test_cross_process",
|
20
|
-
circuit_breaker_options: { cache: Moneta.new(:PStore, file: dbfile) }
|
21
|
-
c.adapter :typhoeus # support in_parallel
|
22
|
-
end
|
23
|
-
@failure_url = "http://127.0.0.1:4713"
|
24
|
-
|
25
|
-
if !@@only_once
|
26
|
-
pid = fork do
|
27
|
-
Rack::Handler::WEBrick.run(lambda { |env| [500, {}, ["Failure"]] },
|
28
|
-
Port: 4713,
|
29
|
-
AccessLog: [],
|
30
|
-
Logger: WEBrick::Log.new(DEV_NULL))
|
31
|
-
end
|
32
|
-
sleep 0.5
|
33
|
-
Minitest.after_run { Process.kill "KILL", pid }
|
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
|
-
c.adapter :typhoeus
|
48
|
-
end
|
49
|
-
open_circuit(con)
|
50
|
-
end
|
51
|
-
Process.wait pid
|
52
|
-
response = connection.get(failure_url)
|
53
|
-
assert response.original_response.nil?, "opening the circuit from a different process should be respected in the main process"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
require "integration_helper"
|
2
|
-
require "typhoeus/adapters/faraday"
|
3
|
-
|
4
|
-
class Circuitbox
|
5
|
-
|
6
|
-
class FaradayMiddlewareTest < Minitest::Test
|
7
|
-
include IntegrationHelpers
|
8
|
-
|
9
|
-
attr_reader :connection, :success_url, :failure_url
|
10
|
-
|
11
|
-
@@only_once = false
|
12
|
-
def setup
|
13
|
-
@connection = Faraday.new do |c|
|
14
|
-
c.use FaradayMiddleware
|
15
|
-
c.adapter :typhoeus # support in_parallel
|
16
|
-
end
|
17
|
-
@success_url = "http://localhost:4711"
|
18
|
-
@failure_url = "http://localhost:4712"
|
19
|
-
|
20
|
-
if !@@only_once
|
21
|
-
FakeServer.create(4711, ['200', {'Content-Type' => 'text/plain'}, ["Success!"]])
|
22
|
-
FakeServer.create(4712, ['500', {'Content-Type' => 'text/plain'}, ["Failure!"]])
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def teardown
|
27
|
-
Circuitbox.reset
|
28
|
-
end
|
29
|
-
|
30
|
-
def test_circuit_does_not_open_for_below_threshhold_failed_requests
|
31
|
-
5.times { connection.get(failure_url) }
|
32
|
-
assert_equal connection.get(success_url).status, 200
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_failure_circuit_response
|
36
|
-
failure_response = connection.get(failure_url)
|
37
|
-
assert_equal failure_response.status, 503
|
38
|
-
assert_match failure_response.original_response.body, "Failure!"
|
39
|
-
end
|
40
|
-
|
41
|
-
def test_open_circuit_response
|
42
|
-
open_circuit
|
43
|
-
open_circuit_response = connection.get(failure_url)
|
44
|
-
assert_equal open_circuit_response.status, 503
|
45
|
-
assert_nil open_circuit_response.original_response
|
46
|
-
assert_kind_of Circuitbox::OpenCircuitError, open_circuit_response.original_exception
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_closed_circuit_response
|
50
|
-
result = connection.get(success_url)
|
51
|
-
assert result.success?
|
52
|
-
end
|
53
|
-
|
54
|
-
def test_parallel_requests_closed_circuit_response
|
55
|
-
response_1, response_2 = nil
|
56
|
-
connection.in_parallel do
|
57
|
-
response_1 = connection.get(success_url)
|
58
|
-
response_2 = connection.get(success_url)
|
59
|
-
end
|
60
|
-
|
61
|
-
assert response_1.success?
|
62
|
-
assert response_2.success?
|
63
|
-
end
|
64
|
-
|
65
|
-
def test_parallel_requests_open_circuit_response
|
66
|
-
open_circuit
|
67
|
-
response_1, response_2 = nil
|
68
|
-
connection.in_parallel do
|
69
|
-
response_1 = connection.get(failure_url)
|
70
|
-
response_2 = connection.get(failure_url)
|
71
|
-
end
|
72
|
-
|
73
|
-
assert_equal response_1.status, 503
|
74
|
-
assert_equal response_2.status, 503
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
end
|
data/test/integration_helper.rb
DELETED
@@ -1,48 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
require "faraday"
|
3
|
-
require "circuitbox/faraday_middleware"
|
4
|
-
require "rack"
|
5
|
-
|
6
|
-
class FakeServer
|
7
|
-
def self.instance
|
8
|
-
@@instance ||= FakeServer.new
|
9
|
-
# if the FakeServer is used kill all of them after the tests are done
|
10
|
-
Minitest.after_run { FakeServer.shutdown }
|
11
|
-
@@instance
|
12
|
-
end
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@servers = []
|
16
|
-
end
|
17
|
-
|
18
|
-
def self.create(port, result)
|
19
|
-
FakeServer.instance.create(port, result)
|
20
|
-
end
|
21
|
-
|
22
|
-
def self.shutdown
|
23
|
-
FakeServer.instance.shutdown
|
24
|
-
end
|
25
|
-
|
26
|
-
def shutdown
|
27
|
-
@servers.map { |server| server.exit }
|
28
|
-
@servers = []
|
29
|
-
end
|
30
|
-
|
31
|
-
def create(port, result)
|
32
|
-
@servers << Thread.new do
|
33
|
-
Rack::Handler::WEBrick.run(Proc.new { |env| result },
|
34
|
-
Port: port,
|
35
|
-
AccessLog: [],
|
36
|
-
Logger: WEBrick::Log.new(DEV_NULL))
|
37
|
-
end
|
38
|
-
sleep 0.5 # wait for the server to spin up
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
module IntegrationHelpers
|
43
|
-
def open_circuit(c = connection)
|
44
|
-
volume_threshold = Circuitbox::CircuitBreaker::DEFAULTS[:volume_threshold]
|
45
|
-
(volume_threshold + 1).times { c.get(failure_url) }
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
data/test/notifier_test.rb
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'circuitbox/notifier'
|
3
|
-
require 'active_support/notifications'
|
4
|
-
|
5
|
-
|
6
|
-
class NotifierTest < Minitest::Test
|
7
|
-
def test_sends_notification_on_notify
|
8
|
-
ActiveSupport::Notifications.expects(:instrument).with("circuit_open", circuit: 'yammer:12')
|
9
|
-
Circuitbox::Notifier.new(:yammer, 12).notify(:open)
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_sends_warning_notificaiton_notify_warning
|
13
|
-
ActiveSupport::Notifications.expects(:instrument).with("circuit_warning", { circuit: 'yammer:12', message: 'hello'})
|
14
|
-
Circuitbox::Notifier.new(:yammer, 12).notify_warning('hello')
|
15
|
-
end
|
16
|
-
|
17
|
-
def test_sends_metric_as_notification
|
18
|
-
ActiveSupport::Notifications.expects(:instrument).with("circuit_gauge", { circuit: 'yammer:12', gauge: 'ratio', value: 12})
|
19
|
-
Circuitbox::Notifier.new(:yammer, 12).metric_gauge(:ratio, 12)
|
20
|
-
end
|
21
|
-
end
|
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class ServiceFailureErrorTest < Minitest::Test
|
4
|
-
class SomeOtherError < StandardError; end;
|
5
|
-
|
6
|
-
attr_reader :error
|
7
|
-
|
8
|
-
def setup
|
9
|
-
raise SomeOtherError, "some other error"
|
10
|
-
rescue => ex
|
11
|
-
@error = ex
|
12
|
-
end
|
13
|
-
|
14
|
-
def test_includes_the_message_of_the_wrapped_exception
|
15
|
-
ex = Circuitbox::ServiceFailureError.new('test', error)
|
16
|
-
assert_equal "Circuitbox::ServiceFailureError wrapped: #{error}", ex.to_s
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_keeps_the_original_backtrace
|
20
|
-
ex = Circuitbox::ServiceFailureError.new('test', error)
|
21
|
-
assert_equal error.backtrace, ex.backtrace
|
22
|
-
end
|
23
|
-
end
|
data/test/test_helper.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'minitest/autorun'
|
2
|
-
require 'mocha/mini_test'
|
3
|
-
require 'timecop'
|
4
|
-
require 'gimme'
|
5
|
-
require 'circuitbox'
|
6
|
-
|
7
|
-
DEV_NULL = (RUBY_PLATFORM =~ /mswin|mingw/ ? "NUL" : "/dev/null")
|
8
|
-
|
9
|
-
class Circuitbox
|
10
|
-
class CircuitBreaker
|
11
|
-
def logger
|
12
|
-
@_dev_null_logger ||= Logger.new(DEV_NULL)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|