circuitbox 1.1.0 → 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.
Files changed (38) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +56 -187
  3. data/lib/circuitbox.rb +14 -57
  4. data/lib/circuitbox/circuit_breaker.rb +137 -161
  5. data/lib/circuitbox/circuit_breaker/logger_messages.rb +31 -0
  6. data/lib/circuitbox/configuration.rb +51 -0
  7. data/lib/circuitbox/errors/error.rb +3 -2
  8. data/lib/circuitbox/errors/open_circuit_error.rb +3 -1
  9. data/lib/circuitbox/errors/service_failure_error.rb +5 -1
  10. data/lib/circuitbox/excon_middleware.rb +23 -30
  11. data/lib/circuitbox/faraday_middleware.rb +43 -63
  12. data/lib/circuitbox/memory_store.rb +85 -0
  13. data/lib/circuitbox/memory_store/container.rb +30 -0
  14. data/lib/circuitbox/memory_store/monotonic_time.rb +13 -0
  15. data/lib/circuitbox/notifier/active_support.rb +19 -0
  16. data/lib/circuitbox/notifier/null.rb +13 -0
  17. data/lib/circuitbox/timer.rb +51 -0
  18. data/lib/circuitbox/version.rb +3 -1
  19. metadata +106 -118
  20. data/.gitignore +0 -20
  21. data/.ruby-version +0 -1
  22. data/.travis.yml +0 -9
  23. data/Gemfile +0 -6
  24. data/Rakefile +0 -18
  25. data/benchmark/circuit_store_benchmark.rb +0 -114
  26. data/circuitbox.gemspec +0 -48
  27. data/lib/circuitbox/memcache_store.rb +0 -31
  28. data/lib/circuitbox/notifier.rb +0 -34
  29. data/test/circuit_breaker_test.rb +0 -428
  30. data/test/circuitbox_test.rb +0 -45
  31. data/test/excon_middleware_test.rb +0 -131
  32. data/test/faraday_middleware_test.rb +0 -175
  33. data/test/integration/circuitbox_cross_process_open_test.rb +0 -56
  34. data/test/integration/faraday_middleware_test.rb +0 -78
  35. data/test/integration_helper.rb +0 -48
  36. data/test/notifier_test.rb +0 -21
  37. data/test/service_failure_error_test.rb +0 -23
  38. 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