resilient 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +7 -2
- data/README.md +14 -0
- data/examples/binary.rb +114 -0
- data/lib/resilient/circuit_breaker.rb +2 -2
- data/lib/resilient/circuit_breaker/metrics.rb +13 -15
- data/lib/resilient/test/metrics_interface.rb +10 -16
- data/lib/resilient/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7441e56169aaa25eeeb4e40a73901f939e3af2d
|
4
|
+
data.tar.gz: 04f4f827c3fc3de69439b40590ab1c3615e43376
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e54a1abc95f5ba114b2eb4064a2d8f0190014268c7061a6869bc7811278f075331ec13cfa5758738be1690e427cfc38ca768d5f34f2258007eece41b9fc707e6
|
7
|
+
data.tar.gz: bf752309fd26f431ae9c04b7dffeb68827bb05835afee1b2beed0548ecbd70333ce53c7983e2855bb2c5ae2bcdfd1f1ce1b70849c03c609f67fe061e31a9cf77
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
## 0.5.0
|
2
|
+
|
3
|
+
* [tweaked interface for metrics](https://github.com/jnunemaker/resilient/pull/15)
|
4
|
+
* [stop rounding error percentage](https://github.com/jnunemaker/resilient/commit/6cdade5c3fc5f75a6ffde87082771608d582026c)
|
5
|
+
|
6
|
+
## prior to 0.5.0
|
7
|
+
|
8
|
+
* No changelog kept so just checkout the commits. There aren't too terrible many.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -82,6 +82,20 @@ circuit_breaker = Resilient::CircuitBreaker.get("example", {
|
|
82
82
|
# etc etc etc
|
83
83
|
```
|
84
84
|
|
85
|
+
## Default Properties
|
86
|
+
|
87
|
+
Property | Default | Notes
|
88
|
+
--------------------------------|------------------------|--------
|
89
|
+
**:force_open** | false | allows forcing the circuit open (stopping all requests)
|
90
|
+
**:force_closed** | false | allows ignoring errors and therefore never trip "open" (ie. allow all traffic through); normal instrumentation will still happen, thus allowing you to "test" configuration live without impact
|
91
|
+
**:instrumenter** | Instrumenters::Noop | what to use to instrument all events that happen (ie: ActiveSupport::Notifications)
|
92
|
+
**:sleep_window_seconds** | 5 | seconds after tripping circuit before allowing retry
|
93
|
+
**:request_volume_threshold** | 20 | number of requests that must be made within a statistical window before open/close decisions are made using stats
|
94
|
+
**:error_threshold_percentage** | 50 | % of "marks" that must be failed to trip the circuit
|
95
|
+
**:window_size_in_seconds** | 60 | number of seconds in the statistical window
|
96
|
+
**:bucket_size_in_seconds** | 10 | size of buckets in statistical window
|
97
|
+
**:metrics** | Resilient::Metrics.new | metrics instance used to keep track of success and failure
|
98
|
+
|
85
99
|
## Tests
|
86
100
|
|
87
101
|
To ensure that you have clean circuit breakers for each test case, be sure to run the following in the setup for your tests (which resets the default registry and thus clears all the registered circuits) either before every test case or at a minimum each test case that uses circuit breakers.
|
data/examples/binary.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
# setting up load path
|
2
|
+
require "pathname"
|
3
|
+
root_path = Pathname(__FILE__).dirname.join("..").expand_path
|
4
|
+
lib_path = root_path.join("lib")
|
5
|
+
$:.unshift(lib_path)
|
6
|
+
|
7
|
+
# requiring stuff for this example
|
8
|
+
require "pp"
|
9
|
+
require "minitest/autorun"
|
10
|
+
require "resilient/circuit_breaker"
|
11
|
+
require "resilient/test/metrics_interface"
|
12
|
+
|
13
|
+
# Metrics class that closes circuit on every success call and opens circuit for
|
14
|
+
# sleep_window_seconds on every failure.
|
15
|
+
class BinaryMetrics
|
16
|
+
def initialize(options = {})
|
17
|
+
reset
|
18
|
+
end
|
19
|
+
|
20
|
+
def success
|
21
|
+
reset
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def failure
|
26
|
+
@closed = false
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset
|
31
|
+
@closed = true
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def under_request_volume_threshold?(request_volume_threshold)
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def under_error_threshold_percentage?(error_threshold_percentage)
|
40
|
+
@closed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class BinaryMetricsTest < Minitest::Test
|
45
|
+
def setup
|
46
|
+
@object = BinaryMetrics.new
|
47
|
+
end
|
48
|
+
|
49
|
+
include Resilient::Test::MetricsInterface
|
50
|
+
end
|
51
|
+
|
52
|
+
circuit_breaker = Resilient::CircuitBreaker.get("example", {
|
53
|
+
sleep_window_seconds: 1,
|
54
|
+
metrics: BinaryMetrics.new,
|
55
|
+
})
|
56
|
+
|
57
|
+
# success
|
58
|
+
if circuit_breaker.allow_request?
|
59
|
+
begin
|
60
|
+
puts "do expensive thing"
|
61
|
+
circuit_breaker.success
|
62
|
+
rescue => boom
|
63
|
+
# won't get here in this example
|
64
|
+
circuit_breaker.failure
|
65
|
+
end
|
66
|
+
else
|
67
|
+
raise "will not get here"
|
68
|
+
end
|
69
|
+
|
70
|
+
# failure
|
71
|
+
if circuit_breaker.allow_request?
|
72
|
+
begin
|
73
|
+
raise
|
74
|
+
rescue => boom
|
75
|
+
circuit_breaker.failure
|
76
|
+
puts "failed slow, do fallback"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
raise "will not get here"
|
80
|
+
end
|
81
|
+
|
82
|
+
# fail fast
|
83
|
+
if circuit_breaker.allow_request?
|
84
|
+
raise "will not get here"
|
85
|
+
else
|
86
|
+
puts "failed fast, do fallback"
|
87
|
+
end
|
88
|
+
|
89
|
+
start = Time.now
|
90
|
+
|
91
|
+
while (Time.now - start) < 3
|
92
|
+
if circuit_breaker.allow_request?
|
93
|
+
puts "doing a single attempt as we've failed fast for sleep_window_seconds"
|
94
|
+
break
|
95
|
+
else
|
96
|
+
puts "failed fast, do fallback"
|
97
|
+
end
|
98
|
+
sleep rand(0.1)
|
99
|
+
end
|
100
|
+
|
101
|
+
if circuit_breaker.allow_request?
|
102
|
+
raise "will not get here"
|
103
|
+
else
|
104
|
+
puts "request denied because single request has not been marked success yet"
|
105
|
+
end
|
106
|
+
|
107
|
+
puts "marking single request as success"
|
108
|
+
circuit_breaker.success
|
109
|
+
|
110
|
+
if circuit_breaker.allow_request?
|
111
|
+
puts "circuit reset and back closed now, allowing requests"
|
112
|
+
else
|
113
|
+
raise "will not get here"
|
114
|
+
end
|
@@ -117,11 +117,11 @@ module Resilient
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def under_request_volume_threshold?
|
120
|
-
metrics.
|
120
|
+
metrics.under_request_volume_threshold?(@properties.request_volume_threshold)
|
121
121
|
end
|
122
122
|
|
123
123
|
def under_error_threshold_percentage?
|
124
|
-
metrics.
|
124
|
+
metrics.under_error_threshold_percentage?(@properties.error_threshold_percentage)
|
125
125
|
end
|
126
126
|
|
127
127
|
def open?
|
@@ -30,6 +30,14 @@ module Resilient
|
|
30
30
|
@buckets = []
|
31
31
|
end
|
32
32
|
|
33
|
+
def under_request_volume_threshold?(request_volume_threshold)
|
34
|
+
requests < request_volume_threshold
|
35
|
+
end
|
36
|
+
|
37
|
+
def under_error_threshold_percentage?(error_threshold_percentage)
|
38
|
+
error_percentage < error_threshold_percentage
|
39
|
+
end
|
40
|
+
|
33
41
|
def success
|
34
42
|
@storage.increment(current_bucket, StorageSuccessKeys)
|
35
43
|
prune_buckets
|
@@ -42,15 +50,12 @@ module Resilient
|
|
42
50
|
nil
|
43
51
|
end
|
44
52
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
53
|
+
def reset
|
54
|
+
@storage.prune(@buckets, StorageKeys)
|
55
|
+
nil
|
48
56
|
end
|
49
57
|
|
50
|
-
|
51
|
-
prune_buckets
|
52
|
-
@storage.sum(@buckets, :failures)[:failures]
|
53
|
-
end
|
58
|
+
private
|
54
59
|
|
55
60
|
def requests
|
56
61
|
prune_buckets
|
@@ -71,16 +76,9 @@ module Resilient
|
|
71
76
|
requests = successes + failures
|
72
77
|
return 0 if failures == 0 || requests == 0
|
73
78
|
|
74
|
-
(
|
79
|
+
(failures / requests.to_f) * 100
|
75
80
|
end
|
76
81
|
|
77
|
-
def reset
|
78
|
-
@storage.prune(@buckets, StorageKeys)
|
79
|
-
nil
|
80
|
-
end
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
82
|
def current_bucket(timestamp = Time.now.to_i)
|
85
83
|
bucket = @buckets.detect { |bucket| bucket.include?(timestamp) }
|
86
84
|
return bucket if bucket
|
@@ -1,6 +1,16 @@
|
|
1
1
|
module Resilient
|
2
2
|
class Test
|
3
3
|
module MetricsInterface
|
4
|
+
def test_responds_to_under_request_volume_threshold_predicate
|
5
|
+
assert_respond_to @object, :under_request_volume_threshold?
|
6
|
+
assert_equal 1, @object.method(:under_request_volume_threshold?).arity
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_responds_to_under_error_threshold_percentage_predicate
|
10
|
+
assert_respond_to @object, :under_error_threshold_percentage?
|
11
|
+
assert_equal 1, @object.method(:under_error_threshold_percentage?).arity
|
12
|
+
end
|
13
|
+
|
4
14
|
def test_responds_to_success
|
5
15
|
assert_respond_to @object, :success
|
6
16
|
end
|
@@ -17,22 +27,6 @@ module Resilient
|
|
17
27
|
assert_nil @object.failure
|
18
28
|
end
|
19
29
|
|
20
|
-
def test_responds_to_successes
|
21
|
-
assert_respond_to @object, :successes
|
22
|
-
end
|
23
|
-
|
24
|
-
def test_responds_to_failures
|
25
|
-
assert_respond_to @object, :failures
|
26
|
-
end
|
27
|
-
|
28
|
-
def test_responds_to_requests
|
29
|
-
assert_respond_to @object, :requests
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_responds_to_error_percentage
|
33
|
-
assert_respond_to @object, :error_percentage
|
34
|
-
end
|
35
|
-
|
36
30
|
def test_responds_to_reset
|
37
31
|
assert_respond_to @object, :reset
|
38
32
|
end
|
data/lib/resilient/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resilient
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-07-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -61,11 +61,13 @@ extra_rdoc_files: []
|
|
61
61
|
files:
|
62
62
|
- ".gitignore"
|
63
63
|
- ".travis.yml"
|
64
|
+
- CHANGELOG.md
|
64
65
|
- Gemfile
|
65
66
|
- Guardfile
|
66
67
|
- LICENSE.txt
|
67
68
|
- README.md
|
68
69
|
- examples/basic.rb
|
70
|
+
- examples/binary.rb
|
69
71
|
- examples/get_vs_new.rb
|
70
72
|
- examples/long_running.rb
|
71
73
|
- lib/resilient.rb
|