circuitbox 1.1.1 → 2.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +60 -122
  3. data/lib/circuitbox.rb +15 -52
  4. data/lib/circuitbox/circuit_breaker.rb +131 -141
  5. data/lib/circuitbox/circuit_breaker/logger_messages.rb +31 -0
  6. data/lib/circuitbox/configuration.rb +53 -0
  7. data/lib/circuitbox/errors/error.rb +1 -2
  8. data/lib/circuitbox/errors/open_circuit_error.rb +0 -1
  9. data/lib/circuitbox/errors/service_failure_error.rb +2 -1
  10. data/lib/circuitbox/excon_middleware.rb +15 -24
  11. data/lib/circuitbox/faraday_middleware.rb +35 -61
  12. data/lib/circuitbox/memory_store.rb +76 -0
  13. data/lib/circuitbox/memory_store/compactor.rb +35 -0
  14. data/lib/circuitbox/memory_store/container.rb +28 -0
  15. data/lib/circuitbox/memory_store/monotonic_time.rb +11 -0
  16. data/lib/circuitbox/notifier/active_support.rb +19 -0
  17. data/lib/circuitbox/notifier/null.rb +11 -0
  18. data/lib/circuitbox/timer/monotonic.rb +17 -0
  19. data/lib/circuitbox/timer/null.rb +9 -0
  20. data/lib/circuitbox/timer/simple.rb +13 -0
  21. data/lib/circuitbox/version.rb +1 -1
  22. metadata +78 -150
  23. data/.gitignore +0 -20
  24. data/.ruby-version +0 -1
  25. data/.travis.yml +0 -9
  26. data/Gemfile +0 -6
  27. data/Rakefile +0 -30
  28. data/benchmark/circuit_store_benchmark.rb +0 -114
  29. data/circuitbox.gemspec +0 -48
  30. data/lib/circuitbox/notifier.rb +0 -34
  31. data/test/circuit_breaker_test.rb +0 -436
  32. data/test/circuitbox_test.rb +0 -45
  33. data/test/excon_middleware_test.rb +0 -131
  34. data/test/faraday_middleware_test.rb +0 -175
  35. data/test/integration/circuitbox_cross_process_open_test.rb +0 -56
  36. data/test/integration/faraday_middleware_test.rb +0 -78
  37. data/test/integration_helper.rb +0 -48
  38. data/test/notifier_test.rb +0 -21
  39. data/test/service_failure_error_test.rb +0 -23
  40. data/test/test_helper.rb +0 -15
@@ -1,45 +0,0 @@
1
- require 'test_helper'
2
-
3
- class CircuitboxTest < Minitest::Test
4
-
5
- def setup
6
- Circuitbox.reset
7
- end
8
-
9
- def test_circuit_store_is_configurable
10
- store = Moneta.new(:Memory, expires: true)
11
- Circuitbox.circuit_store = store
12
- assert_equal store, Circuitbox[:yammer].circuit_store
13
- end
14
-
15
- def test_delegates_to_circuit
16
- Circuitbox.expects(:circuit).with(:yammer, {})
17
- Circuitbox[:yammer]
18
- end
19
-
20
- def test_creates_a_circuit_breaker
21
- assert Circuitbox[:yammer].is_a? Circuitbox::CircuitBreaker
22
- end
23
-
24
- def test_returns_the_same_circuit_every_time
25
- assert_equal Circuitbox.circuit(:yammer), Circuitbox.circuit(:yammer)
26
- end
27
-
28
- def test_sets_the_circuit_options_the_first_time_only
29
- circuit_one = Circuitbox.circuit(:yammer, :sleep_window => 1337)
30
- circuit_two = Circuitbox.circuit(:yammer, :sleep_window => 2000)
31
-
32
- assert_equal 1337, circuit_one.option_value(:sleep_window)
33
- assert_equal 1337, circuit_two.option_value(:sleep_window)
34
- end
35
-
36
- def test_uses_parsed_uri_host_as_identifier_for_circuit
37
- service = Circuitbox.parameter_to_service_name("http://api.yammer.com/api/v1/messages")
38
- assert_equal "api.yammer.com", service
39
- end
40
-
41
- def test_uses_identifier_directly_for_circuit_if_it_is_not_an_uri
42
- service = Circuitbox.parameter_to_service_name(:yammer)
43
- assert_equal "yammer", service
44
- end
45
- end
@@ -1,131 +0,0 @@
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
@@ -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