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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c930c989a3184cfc4fb254c720a854caf34fb56bbb550df150d65125969240b2
4
- data.tar.gz: 3ca98448ca2a378d6101e4b10e4567adb24bc65c2747d77bccf4b9386aeba64c
3
+ metadata.gz: e14702003b76fde01b28e5266b392f5d55b2924c83365a0ab122f11aebf2e4a7
4
+ data.tar.gz: e9c1b348b2637aa66408de62a2bbbc24b9b73927ca9712a8b4137446e336cd0a
5
5
  SHA512:
6
- metadata.gz: caf3fbc0b32e823a3877525841ab46948fdedbda6791da8006ded3c194229aad7add1c01df82f60b406606a34481a40535662036b2f21caf1ea953ed58ae3eab
7
- data.tar.gz: 0d372655a21b4f2ebf04973a6fa41ae4e24e1bab2851bfef69c6ba866a2e38231cdc2a13f4c3afb7a267f56c7a5e3040d88c4ce0602f6b4392b9216fd57d6f2a
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(EmptyMetadata, config) do
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(EmptyMetadata, config) do
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(EmptyMetadata, config) do
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(EmptyMetadata, config) do
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, (window_end - config.window_size + 1)].compact.max
39
- (window_start..window_end)
39
+ window_start = [recovered_at, (current_time - config.window_size)].compact.max
40
+ (window_start..current_time)
40
41
  else
41
- (..window_end)
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
- window_end = Time.now
98
- window_end_ts = window_end.to_i
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
- successes: successes,
137
- errors: errors,
138
- recovery_probe_successes: recovery_probe_successes,
139
- recovery_probe_errors: recovery_probe_errors,
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
- def execute(fallback, &code)
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::GREEN, Color::YELLOW, nil)
77
+ notifier.notify(config, Color::RED, Color::YELLOW, nil)
64
78
  end
65
79
  end
66
80
  when TrafficRecovery::RED
@@ -32,10 +32,7 @@ module Stoplight
32
32
  #
33
33
  # @return [String]
34
34
  def state
35
- config
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
- config
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
- strategy = state_strategy_factory(color)
76
- strategy.execute(fallback, &code)
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
@@ -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(at: Time.now)
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 < at) || recovery_started_at
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Stoplight
4
- VERSION = Gem::Version.new("5.3.5")
4
+ VERSION = Gem::Version.new("5.3.8")
5
5
  end
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.5
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
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Stoplight
4
- EmptyMetadata = Metadata.new
5
- end