stoplight 5.3.5 → 5.3.8
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 +4 -4
- data/lib/stoplight/data_store/fail_safe.rb +4 -4
- data/lib/stoplight/data_store/memory.rb +15 -11
- data/lib/stoplight/data_store/redis.rb +7 -6
- data/lib/stoplight/light/green_run_strategy.rb +2 -1
- data/lib/stoplight/light/red_run_strategy.rb +2 -1
- data/lib/stoplight/light/run_strategy.rb +3 -1
- data/lib/stoplight/light/yellow_run_strategy.rb +16 -2
- data/lib/stoplight/light.rb +11 -10
- data/lib/stoplight/metadata.rb +7 -5
- data/lib/stoplight/version.rb +1 -1
- metadata +1 -2
- data/lib/stoplight/empty_metadata.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e14702003b76fde01b28e5266b392f5d55b2924c83365a0ab122f11aebf2e4a7
|
4
|
+
data.tar.gz: e9c1b348b2637aa66408de62a2bbbc24b9b73927ca9712a8b4137446e336cd0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1601dd8a7fae2e9c8f89a52556c8f931d3712d71648300aef0e3dae226c9998b7b1fa35e7fa0ce151d943e961473e012ca382b718bb50b44000f5a43e4d667b8
|
7
|
+
data.tar.gz: 7c4e9696009e01bea1908cd3212d4625cc0ac3e03205d1cd69e4471f9aec4f3add7f101e58b393df210658e6be2c2d42b5d4a2ddedd954f832053505e291f232
|
@@ -48,13 +48,13 @@ module Stoplight
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def get_metadata(config)
|
51
|
-
with_fallback(
|
51
|
+
with_fallback(Metadata.new, config) do
|
52
52
|
data_store.get_metadata(config)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
56
|
def record_failure(config, failure)
|
57
|
-
with_fallback(
|
57
|
+
with_fallback(Metadata.new, config) do
|
58
58
|
data_store.record_failure(config, failure)
|
59
59
|
end
|
60
60
|
end
|
@@ -66,13 +66,13 @@ module Stoplight
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def record_recovery_probe_success(config, **args)
|
69
|
-
with_fallback(
|
69
|
+
with_fallback(Metadata.new, config) do
|
70
70
|
data_store.record_recovery_probe_success(config, **args)
|
71
71
|
end
|
72
72
|
end
|
73
73
|
|
74
74
|
def record_recovery_probe_failure(config, failure)
|
75
|
-
with_fallback(
|
75
|
+
with_fallback(Metadata.new, config) do
|
76
76
|
data_store.record_recovery_probe_failure(config, failure)
|
77
77
|
end
|
78
78
|
end
|
@@ -7,6 +7,7 @@ module Stoplight
|
|
7
7
|
# @see Base
|
8
8
|
class Memory < Base
|
9
9
|
include MonitorMixin
|
10
|
+
|
10
11
|
KEY_SEPARATOR = ":"
|
11
12
|
|
12
13
|
def initialize
|
@@ -29,16 +30,16 @@ module Stoplight
|
|
29
30
|
# @return [Stoplight::Metadata]
|
30
31
|
def get_metadata(config)
|
31
32
|
light_name = config.name
|
32
|
-
window_end = Time.now
|
33
|
-
recovery_window = (window_end - config.cool_off_time + 1)..window_end
|
34
33
|
|
35
34
|
synchronize do
|
35
|
+
current_time = Time.now
|
36
|
+
recovery_window = (current_time - config.cool_off_time)..current_time
|
36
37
|
recovered_at = @metadata[light_name].recovered_at
|
37
38
|
window = if config.window_size
|
38
|
-
window_start = [recovered_at, (
|
39
|
-
(window_start..
|
39
|
+
window_start = [recovered_at, (current_time - config.window_size)].compact.max
|
40
|
+
(window_start..current_time)
|
40
41
|
else
|
41
|
-
(..
|
42
|
+
(..current_time)
|
42
43
|
end
|
43
44
|
|
44
45
|
errors = @errors[config.name].count do |request_time|
|
@@ -57,6 +58,7 @@ module Stoplight
|
|
57
58
|
end
|
58
59
|
|
59
60
|
@metadata[light_name].with(
|
61
|
+
current_time:,
|
60
62
|
errors:,
|
61
63
|
successes:,
|
62
64
|
recovery_probe_errors:,
|
@@ -170,17 +172,14 @@ module Stoplight
|
|
170
172
|
cleanup(@recovery_probe_successes[light_name], window_size: config.cool_off_time)
|
171
173
|
|
172
174
|
metadata = @metadata[light_name]
|
173
|
-
recovery_started_at = metadata.recovery_started_at || request_time
|
174
175
|
@metadata[light_name] = if metadata.last_success_at.nil? || request_time > metadata.last_success_at
|
175
176
|
metadata.with(
|
176
177
|
last_success_at: request_time,
|
177
|
-
recovery_started_at:,
|
178
178
|
consecutive_errors: 0,
|
179
179
|
consecutive_successes: metadata.consecutive_successes.succ
|
180
180
|
)
|
181
181
|
else
|
182
182
|
metadata.with(
|
183
|
-
recovery_started_at:,
|
184
183
|
consecutive_errors: 0,
|
185
184
|
consecutive_successes: metadata.consecutive_successes.succ
|
186
185
|
)
|
@@ -259,9 +258,7 @@ module Stoplight
|
|
259
258
|
|
260
259
|
synchronize do
|
261
260
|
metadata = @metadata[light_name]
|
262
|
-
if metadata.recovery_started_at
|
263
|
-
false
|
264
|
-
else
|
261
|
+
if metadata.recovery_started_at.nil?
|
265
262
|
@metadata[light_name] = metadata.with(
|
266
263
|
recovery_started_at: current_time,
|
267
264
|
recovery_scheduled_after: nil,
|
@@ -269,6 +266,13 @@ module Stoplight
|
|
269
266
|
breached_at: nil
|
270
267
|
)
|
271
268
|
true
|
269
|
+
else
|
270
|
+
@metadata[light_name] = metadata.with(
|
271
|
+
recovery_scheduled_after: nil,
|
272
|
+
recovered_at: nil,
|
273
|
+
breached_at: nil
|
274
|
+
)
|
275
|
+
false
|
272
276
|
end
|
273
277
|
end
|
274
278
|
end
|
@@ -94,8 +94,8 @@ module Stoplight
|
|
94
94
|
def get_metadata(config)
|
95
95
|
detect_clock_skew
|
96
96
|
|
97
|
-
|
98
|
-
window_end_ts =
|
97
|
+
current_time = Time.now
|
98
|
+
window_end_ts = current_time.to_i
|
99
99
|
window_start_ts = window_end_ts - [config.window_size, Base::METRICS_RETENTION_TIME].compact.min.to_i
|
100
100
|
recovery_window_start_ts = window_end_ts - config.cool_off_time.to_i
|
101
101
|
|
@@ -133,10 +133,11 @@ module Stoplight
|
|
133
133
|
last_error = normalize_failure(last_error_json, config.error_notifier) if last_error_json
|
134
134
|
|
135
135
|
Metadata.new(
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
136
|
+
current_time:,
|
137
|
+
successes:,
|
138
|
+
errors:,
|
139
|
+
recovery_probe_successes:,
|
140
|
+
recovery_probe_errors:,
|
140
141
|
last_error:,
|
141
142
|
**meta_hash
|
142
143
|
)
|
@@ -12,10 +12,11 @@ module Stoplight
|
|
12
12
|
# Executes the provided code block when the light is in the green state.
|
13
13
|
#
|
14
14
|
# @param fallback [Proc, nil] A fallback proc to execute in case of an error.
|
15
|
+
# @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
|
15
16
|
# @yield The code block to execute.
|
16
17
|
# @return [Object] The result of the code block if successful.
|
17
18
|
# @raise [Exception] Re-raises the error if it is not tracked or no fallback is provided.
|
18
|
-
def execute(fallback, &code)
|
19
|
+
def execute(fallback, metadata:, &code)
|
19
20
|
# TODO: Consider implementing sampling rate to limit the memory footprint
|
20
21
|
code.call.tap { record_success }
|
21
22
|
rescue => error
|
@@ -12,9 +12,10 @@ module Stoplight
|
|
12
12
|
# Executes the fallback proc when the light is in the red state.
|
13
13
|
#
|
14
14
|
# @param fallback [Proc, nil] A fallback proc to execute instead of the code block.
|
15
|
+
# @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
|
15
16
|
# @return [Object, nil] The result of the fallback proc if provided.
|
16
17
|
# @raise [Stoplight::Error::RedLight] Raises an error if no fallback is provided.
|
17
|
-
def execute(fallback)
|
18
|
+
def execute(fallback, metadata:)
|
18
19
|
if fallback
|
19
20
|
fallback.call(nil)
|
20
21
|
else
|
@@ -22,7 +22,9 @@ module Stoplight
|
|
22
22
|
@data_store = config.data_store
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
# @param fallback [Proc, nil] A fallback proc to execute in case of an error.
|
26
|
+
# @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
|
27
|
+
def execute(fallback, metadata:, &code)
|
26
28
|
raise NotImplementedError, "Subclasses must implement the execute method"
|
27
29
|
end
|
28
30
|
end
|
@@ -13,10 +13,12 @@ module Stoplight
|
|
13
13
|
# Executes the provided code block when the light is in the yellow state.
|
14
14
|
#
|
15
15
|
# @param fallback [Proc, nil] A fallback proc to execute in case of an error.
|
16
|
+
# @param metadata [Stoplight::Metadata] Metadata capturing the current state of the light.
|
16
17
|
# @yield The code block to execute.
|
17
18
|
# @return [Object] The result of the code block if successful.
|
18
19
|
# @raise [Exception] Re-raises the error if it is not tracked or no fallback is provided.
|
19
|
-
def execute(fallback, &code)
|
20
|
+
def execute(fallback, metadata:, &code)
|
21
|
+
transition_to_yellow(metadata:)
|
20
22
|
# TODO: We need to employ a probabilistic approach here to avoid "thundering herd" problem
|
21
23
|
code.call.tap { record_recovery_probe_success }
|
22
24
|
rescue => error
|
@@ -47,6 +49,18 @@ module Stoplight
|
|
47
49
|
recover(metadata)
|
48
50
|
end
|
49
51
|
|
52
|
+
# @param metadata [Stoplight::Metadata]
|
53
|
+
# @return [void]
|
54
|
+
def transition_to_yellow(metadata:)
|
55
|
+
return unless metadata.color == Color::YELLOW
|
56
|
+
|
57
|
+
if metadata.recovery_scheduled_after && config.data_store.transition_to_color(config, Color::YELLOW)
|
58
|
+
config.notifiers.each do |notifier|
|
59
|
+
notifier.notify(config, Color::RED, Color::YELLOW, nil)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
50
64
|
private def recover(metadata)
|
51
65
|
recovery_result = config.traffic_recovery.determine_color(config, metadata)
|
52
66
|
|
@@ -60,7 +74,7 @@ module Stoplight
|
|
60
74
|
when TrafficRecovery::YELLOW
|
61
75
|
if data_store.transition_to_color(config, Color::YELLOW)
|
62
76
|
config.notifiers.each do |notifier|
|
63
|
-
notifier.notify(config, Color::
|
77
|
+
notifier.notify(config, Color::RED, Color::YELLOW, nil)
|
64
78
|
end
|
65
79
|
end
|
66
80
|
when TrafficRecovery::RED
|
data/lib/stoplight/light.rb
CHANGED
@@ -32,10 +32,7 @@ module Stoplight
|
|
32
32
|
#
|
33
33
|
# @return [String]
|
34
34
|
def state
|
35
|
-
|
36
|
-
.data_store
|
37
|
-
.get_metadata(config)
|
38
|
-
.locked_state
|
35
|
+
metadata.locked_state
|
39
36
|
end
|
40
37
|
|
41
38
|
# Returns current color:
|
@@ -49,10 +46,7 @@ module Stoplight
|
|
49
46
|
#
|
50
47
|
# @return [String] returns current light color
|
51
48
|
def color
|
52
|
-
|
53
|
-
.data_store
|
54
|
-
.get_metadata(config)
|
55
|
-
.color
|
49
|
+
metadata.color
|
56
50
|
end
|
57
51
|
|
58
52
|
# Runs the given block of code with this circuit breaker
|
@@ -72,8 +66,10 @@ module Stoplight
|
|
72
66
|
def run(fallback = nil, &code)
|
73
67
|
raise ArgumentError, "nothing to run. Please, pass a block into `Light#run`" unless block_given?
|
74
68
|
|
75
|
-
|
76
|
-
|
69
|
+
metadata.then do |metadata|
|
70
|
+
strategy = state_strategy_factory(metadata.color)
|
71
|
+
strategy.execute(fallback, metadata:, &code)
|
72
|
+
end
|
77
73
|
end
|
78
74
|
|
79
75
|
# Locks light in either +State::LOCKED_RED+ or +State::LOCKED_GREEN+
|
@@ -186,5 +182,10 @@ module Stoplight
|
|
186
182
|
def reconfigure(config)
|
187
183
|
self.class.new(config)
|
188
184
|
end
|
185
|
+
|
186
|
+
# @return [Stoplight::Metadata]
|
187
|
+
def metadata
|
188
|
+
config.data_store.get_metadata(config)
|
189
|
+
end
|
189
190
|
end
|
190
191
|
end
|
data/lib/stoplight/metadata.rb
CHANGED
@@ -16,9 +16,11 @@ module Stoplight
|
|
16
16
|
:locked_state,
|
17
17
|
:recovery_scheduled_after,
|
18
18
|
:recovery_started_at,
|
19
|
-
:recovered_at
|
19
|
+
:recovered_at,
|
20
|
+
:current_time
|
20
21
|
) do
|
21
22
|
def initialize(
|
23
|
+
current_time: Time.now,
|
22
24
|
successes: 0,
|
23
25
|
errors: 0,
|
24
26
|
recovery_probe_successes: 0,
|
@@ -49,6 +51,7 @@ module Stoplight
|
|
49
51
|
recovery_scheduled_after: (Time.at(Integer(recovery_scheduled_after)) if recovery_scheduled_after),
|
50
52
|
recovery_started_at: (Time.at(Integer(recovery_started_at)) if recovery_started_at),
|
51
53
|
recovered_at: (Time.at(Integer(recovered_at)) if recovered_at),
|
54
|
+
current_time:,
|
52
55
|
)
|
53
56
|
end
|
54
57
|
|
@@ -59,17 +62,16 @@ module Stoplight
|
|
59
62
|
# @param kwargs [Hash{Symbol => Object}]
|
60
63
|
# @return [Metadata]
|
61
64
|
def with(**kwargs)
|
62
|
-
self.class.new(**to_h.merge(kwargs))
|
65
|
+
self.class.new(**to_h.merge(current_time: Time.now, **kwargs))
|
63
66
|
end
|
64
67
|
|
65
|
-
# @param at [Time] (Time.now) the moment of time when the color is determined
|
66
68
|
# @return [String] one of +Color::GREEN+, +Color::RED+, or +Color::YELLOW+
|
67
|
-
def color
|
69
|
+
def color
|
68
70
|
if locked_state == State::LOCKED_GREEN
|
69
71
|
Color::GREEN
|
70
72
|
elsif locked_state == State::LOCKED_RED
|
71
73
|
Color::RED
|
72
|
-
elsif (recovery_scheduled_after && recovery_scheduled_after <
|
74
|
+
elsif (recovery_scheduled_after && recovery_scheduled_after < current_time) || recovery_started_at
|
73
75
|
Color::YELLOW
|
74
76
|
elsif breached_at
|
75
77
|
Color::RED
|
data/lib/stoplight/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stoplight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.3.
|
4
|
+
version: 5.3.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cameron Desautels
|
@@ -76,7 +76,6 @@ files:
|
|
76
76
|
- lib/stoplight/data_store/redis/transition_to_red.lua
|
77
77
|
- lib/stoplight/data_store/redis/transition_to_yellow.lua
|
78
78
|
- lib/stoplight/default.rb
|
79
|
-
- lib/stoplight/empty_metadata.rb
|
80
79
|
- lib/stoplight/error.rb
|
81
80
|
- lib/stoplight/failure.rb
|
82
81
|
- lib/stoplight/light.rb
|