stoplight 5.7.0 → 5.8.0

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 (235) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/UPGRADING.md +303 -0
  4. data/lib/generators/stoplight/install/install_generator.rb +6 -1
  5. data/lib/stoplight/admin/dependencies.rb +1 -1
  6. data/lib/stoplight/admin/helpers.rb +20 -4
  7. data/lib/stoplight/admin/lights_repository/light.rb +22 -6
  8. data/lib/stoplight/admin/lights_repository.rb +6 -5
  9. data/lib/stoplight/admin/views/_card.erb +8 -5
  10. data/lib/stoplight/color.rb +9 -0
  11. data/lib/stoplight/data_store.rb +28 -0
  12. data/lib/stoplight/domain/compatibility_result.rb +7 -7
  13. data/lib/stoplight/domain/config.rb +38 -39
  14. data/lib/stoplight/domain/error_tracking_policy.rb +27 -0
  15. data/lib/stoplight/domain/failure.rb +1 -1
  16. data/lib/stoplight/domain/light/configuration_builder_interface.rb +2 -0
  17. data/lib/stoplight/domain/light.rb +15 -46
  18. data/lib/stoplight/domain/light_info.rb +7 -0
  19. data/lib/stoplight/domain/metrics_snapshot.rb +58 -0
  20. data/lib/stoplight/domain/state_snapshot.rb +29 -23
  21. data/lib/stoplight/domain/storage/recovery_lock_token.rb +15 -0
  22. data/lib/stoplight/domain/strategies/green_run_strategy.rb +18 -26
  23. data/lib/stoplight/domain/strategies/red_run_strategy.rb +9 -12
  24. data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +41 -51
  25. data/lib/stoplight/domain/tracker/recovery_probe.rb +16 -33
  26. data/lib/stoplight/domain/tracker/request.rb +12 -31
  27. data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +8 -11
  28. data/lib/stoplight/domain/traffic_control/error_rate.rb +19 -15
  29. data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +6 -10
  30. data/lib/stoplight/domain/traffic_recovery.rb +3 -4
  31. data/lib/stoplight/error.rb +46 -0
  32. data/lib/stoplight/infrastructure/{data_store/fail_safe.rb → fail_safe/data_store.rb} +39 -51
  33. data/lib/stoplight/infrastructure/fail_safe/storage/metrics.rb +65 -0
  34. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock.rb +69 -0
  35. data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rb +19 -0
  36. data/lib/stoplight/infrastructure/fail_safe/storage/state.rb +62 -0
  37. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/metrics.rb +2 -2
  38. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_store.rb +10 -12
  39. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_token.rb +3 -6
  40. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/sliding_window.rb +21 -26
  41. data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/state.rb +3 -3
  42. data/lib/stoplight/infrastructure/{data_store/memory.rb → memory/data_store.rb} +36 -32
  43. data/lib/stoplight/infrastructure/memory/storage/recovery_lock.rb +35 -0
  44. data/lib/stoplight/infrastructure/memory/storage/recovery_metrics.rb +16 -0
  45. data/lib/stoplight/infrastructure/memory/storage/state.rb +155 -0
  46. data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +103 -0
  47. data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +101 -0
  48. data/lib/stoplight/infrastructure/notifier/fail_safe.rb +9 -21
  49. data/lib/stoplight/infrastructure/notifier/generic.rb +4 -14
  50. data/lib/stoplight/infrastructure/notifier/io.rb +1 -2
  51. data/lib/stoplight/infrastructure/notifier/logger.rb +1 -2
  52. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_store.rb +9 -22
  53. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_token.rb +7 -14
  54. data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/scripting.rb +22 -20
  55. data/lib/stoplight/infrastructure/{data_store/redis.rb → redis/data_store.rb} +48 -56
  56. data/lib/stoplight/infrastructure/redis/storage/key_space.rb +51 -0
  57. data/lib/stoplight/infrastructure/redis/storage/metrics.rb +40 -0
  58. data/lib/stoplight/infrastructure/redis/storage/recovery_lock/release_lock.lua +6 -0
  59. data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +64 -0
  60. data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +20 -0
  61. data/lib/stoplight/infrastructure/redis/storage/scripting.rb +18 -0
  62. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +10 -0
  63. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +10 -0
  64. data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +9 -0
  65. data/lib/stoplight/infrastructure/redis/storage/state.rb +141 -0
  66. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +28 -0
  67. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +26 -0
  68. data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +123 -0
  69. data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +26 -0
  70. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +36 -0
  71. data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +35 -0
  72. data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +174 -0
  73. data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +3 -10
  74. data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +8 -11
  75. data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +6 -14
  76. data/lib/stoplight/infrastructure/storage/compatibility_state.rb +6 -17
  77. data/lib/stoplight/infrastructure/system_clock.rb +16 -0
  78. data/lib/stoplight/notifier.rb +11 -0
  79. data/lib/stoplight/state.rb +9 -0
  80. data/lib/stoplight/types.rb +29 -0
  81. data/lib/stoplight/undefined.rb +16 -0
  82. data/lib/stoplight/version.rb +1 -1
  83. data/lib/stoplight/wiring/config_compatibility_validator.rb +54 -0
  84. data/lib/stoplight/wiring/configuration_dsl.rb +101 -0
  85. data/lib/stoplight/wiring/data_store_backend.rb +26 -0
  86. data/lib/stoplight/wiring/default.rb +1 -1
  87. data/lib/stoplight/wiring/default_config.rb +21 -0
  88. data/lib/stoplight/wiring/default_configuration.rb +70 -53
  89. data/lib/stoplight/wiring/light_builder.rb +76 -63
  90. data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +3 -3
  91. data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +4 -4
  92. data/lib/stoplight/wiring/light_factory.rb +78 -52
  93. data/lib/stoplight/wiring/memory/backend.rb +57 -0
  94. data/lib/stoplight/wiring/redis/backend.rb +116 -0
  95. data/lib/stoplight/wiring/storage_set.rb +12 -0
  96. data/lib/stoplight/wiring/storage_set_builder.rb +51 -0
  97. data/lib/stoplight/wiring/system/light_builder.rb +47 -0
  98. data/lib/stoplight/wiring/system/light_factory.rb +64 -0
  99. data/lib/stoplight/wiring/system.rb +129 -0
  100. data/lib/stoplight.rb +196 -25
  101. data/sig/_private/generators/stoplight/install/install_generator.rbs +22 -0
  102. data/sig/_private/stoplight/common/deprecations.rbs +9 -0
  103. data/sig/_private/stoplight/data_store.rbs +6 -0
  104. data/sig/_private/stoplight/domain/compatibility_result.rbs +18 -0
  105. data/sig/_private/stoplight/domain/config.rbs +65 -0
  106. data/sig/_private/stoplight/domain/error_tracking_policy.rbs +14 -0
  107. data/sig/_private/stoplight/domain/failure.rbs +16 -0
  108. data/sig/_private/stoplight/domain/light.rbs +25 -0
  109. data/sig/_private/stoplight/domain/light_info.rbs +19 -0
  110. data/sig/_private/stoplight/domain/metrics_snapshot.rbs +38 -0
  111. data/sig/_private/stoplight/domain/ports/clock.rbs +18 -0
  112. data/sig/_private/stoplight/domain/ports/data_store.rbs +76 -0
  113. data/{lib/stoplight/domain/light_factory.rb → sig/_private/stoplight/domain/ports/light_factory.rbs} +33 -28
  114. data/sig/_private/stoplight/domain/ports/metrics_store.rbs +29 -0
  115. data/sig/_private/stoplight/domain/ports/recovery_lock_store.rbs +52 -0
  116. data/sig/_private/stoplight/domain/ports/recovery_lock_token.rbs +6 -0
  117. data/sig/_private/stoplight/domain/ports/run_strategy.rbs +14 -0
  118. data/sig/_private/stoplight/domain/ports/state_store.rbs +79 -0
  119. data/sig/_private/stoplight/domain/ports/traffic_control.rbs +41 -0
  120. data/sig/_private/stoplight/domain/ports/traffic_recovery.rbs +47 -0
  121. data/sig/_private/stoplight/domain/state_snapshot.rbs +32 -0
  122. data/sig/_private/stoplight/domain/storage/recovery_lock_token.rbs +11 -0
  123. data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +17 -0
  124. data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +17 -0
  125. data/sig/_private/stoplight/domain/strategies/yellow_run_strategy.rbs +42 -0
  126. data/{lib/stoplight/domain/tracker/base.rb → sig/_private/stoplight/domain/tracker/base.rbs} +0 -4
  127. data/sig/_private/stoplight/domain/tracker/recovery_probe.rbs +25 -0
  128. data/sig/_private/stoplight/domain/tracker/request.rbs +26 -0
  129. data/sig/_private/stoplight/domain/traffic_control/consecutive_errors.rbs +9 -0
  130. data/sig/_private/stoplight/domain/traffic_control/error_rate.rbs +13 -0
  131. data/sig/_private/stoplight/domain/traffic_recovery/consecutive_successes.rbs +9 -0
  132. data/sig/_private/stoplight/domain/traffic_recovery.rbs +9 -0
  133. data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +26 -0
  134. data/sig/_private/stoplight/infrastructure/fail_safe/storage/metrics.rbs +25 -0
  135. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock.rbs +29 -0
  136. data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rbs +19 -0
  137. data/sig/_private/stoplight/infrastructure/fail_safe/storage/state.rbs +25 -0
  138. data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +25 -0
  139. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +19 -0
  140. data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +17 -0
  141. data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +27 -0
  142. data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +17 -0
  143. data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +30 -0
  144. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_lock.rbs +15 -0
  145. data/sig/_private/stoplight/infrastructure/memory/storage/recovery_metrics.rbs +10 -0
  146. data/sig/_private/stoplight/infrastructure/memory/storage/state.rbs +28 -0
  147. data/sig/_private/stoplight/infrastructure/memory/storage/unbounded_metrics.rbs +25 -0
  148. data/sig/_private/stoplight/infrastructure/memory/storage/window_metrics.rbs +26 -0
  149. data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +17 -0
  150. data/sig/_private/stoplight/infrastructure/notifier/generic.rbs +18 -0
  151. data/sig/_private/stoplight/infrastructure/notifier/io.rbs +14 -0
  152. data/sig/_private/stoplight/infrastructure/notifier/logger.rbs +14 -0
  153. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +24 -0
  154. data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +21 -0
  155. data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +34 -0
  156. data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +67 -0
  157. data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +19 -0
  158. data/sig/_private/stoplight/infrastructure/redis/storage/metrics.rbs +17 -0
  159. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_lock.rbs +26 -0
  160. data/sig/_private/stoplight/infrastructure/redis/storage/recovery_metrics.rbs +10 -0
  161. data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +13 -0
  162. data/sig/_private/stoplight/infrastructure/redis/storage/state.rbs +32 -0
  163. data/sig/_private/stoplight/infrastructure/redis/storage/unbounded_metrics.rbs +21 -0
  164. data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +34 -0
  165. data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +17 -0
  166. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +13 -0
  167. data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +14 -0
  168. data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +14 -0
  169. data/sig/_private/stoplight/infrastructure/system_clock.rbs +7 -0
  170. data/sig/_private/stoplight/system/light_builder.rbs +23 -0
  171. data/sig/_private/stoplight/system/light_factory.rbs +17 -0
  172. data/sig/_private/stoplight/types.rbs +6 -0
  173. data/sig/_private/stoplight/wiring/config_compatibility_validator.rbs +19 -0
  174. data/sig/_private/stoplight/wiring/configuration_dsl.rbs +43 -0
  175. data/sig/_private/stoplight/wiring/data_store_backend.rbs +11 -0
  176. data/sig/_private/stoplight/wiring/default.rbs +26 -0
  177. data/{lib/stoplight/wiring/data_store/memory.rb → sig/_private/stoplight/wiring/default_config.rbs} +1 -4
  178. data/sig/_private/stoplight/wiring/default_configuration.rbs +29 -0
  179. data/sig/_private/stoplight/wiring/light_builder.rbs +48 -0
  180. data/sig/_private/stoplight/wiring/light_factory/traffic_control_dsl.rbs +7 -0
  181. data/sig/_private/stoplight/wiring/light_factory/traffic_recovery_dsl.rbs +7 -0
  182. data/sig/_private/stoplight/wiring/light_factory.rbs +16 -0
  183. data/sig/_private/stoplight/wiring/memory/backend.rbs +26 -0
  184. data/sig/_private/stoplight/wiring/notifier_factory.rbs +10 -0
  185. data/sig/_private/stoplight/wiring/redis/backend.rbs +38 -0
  186. data/sig/_private/stoplight/wiring/storage_set.rbs +38 -0
  187. data/sig/_private/stoplight/wiring/storage_set_builder.rbs +15 -0
  188. data/sig/_private/stoplight/wiring/system.rbs +15 -0
  189. data/sig/_private/stoplight.rbs +48 -0
  190. data/sig/stoplight/color.rbs +7 -0
  191. data/sig/stoplight/data_store.rbs +19 -0
  192. data/sig/stoplight/error.rbs +20 -0
  193. data/sig/stoplight/notifier.rbs +11 -0
  194. data/sig/stoplight/ports/configuration.rbs +19 -0
  195. data/sig/stoplight/ports/exception_matcher.rbs +8 -0
  196. data/sig/stoplight/ports/light.rbs +12 -0
  197. data/sig/stoplight/ports/light_info.rbs +5 -0
  198. data/sig/stoplight/ports/state_transition_notifier.rbs +15 -0
  199. data/sig/stoplight/ports/system.rbs +21 -0
  200. data/sig/stoplight/state.rbs +7 -0
  201. data/sig/stoplight/undefined.rbs +9 -0
  202. data/sig/stoplight/version.rbs +3 -0
  203. data/sig/stoplight.rbs +66 -0
  204. metadata +175 -47
  205. data/lib/stoplight/domain/color.rb +0 -11
  206. data/lib/stoplight/domain/data_store.rb +0 -146
  207. data/lib/stoplight/domain/error.rb +0 -42
  208. data/lib/stoplight/domain/metrics.rb +0 -64
  209. data/lib/stoplight/domain/recovery_lock_token.rb +0 -15
  210. data/lib/stoplight/domain/state.rb +0 -11
  211. data/lib/stoplight/domain/state_transition_notifier.rb +0 -25
  212. data/lib/stoplight/domain/storage/metrics.rb +0 -42
  213. data/lib/stoplight/domain/storage/recovery_lock.rb +0 -56
  214. data/lib/stoplight/domain/storage/state.rb +0 -87
  215. data/lib/stoplight/domain/strategies/run_strategy.rb +0 -22
  216. data/lib/stoplight/domain/traffic_control/base.rb +0 -74
  217. data/lib/stoplight/domain/traffic_recovery/base.rb +0 -79
  218. data/lib/stoplight/wiring/data_store/base.rb +0 -11
  219. data/lib/stoplight/wiring/data_store/redis.rb +0 -25
  220. data/lib/stoplight/wiring/default_factory_builder.rb +0 -25
  221. data/lib/stoplight/wiring/light/default_config.rb +0 -18
  222. data/lib/stoplight/wiring/light/system_config.rb +0 -11
  223. data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +0 -55
  224. data/lib/stoplight/wiring/light_factory/config_normalizer.rb +0 -71
  225. data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +0 -72
  226. data/lib/stoplight/wiring/public_api.rb +0 -29
  227. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/get_metrics.lua +0 -0
  228. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_failure.lua +0 -0
  229. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_failure.lua +0 -0
  230. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_success.lua +0 -0
  231. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_success.lua +0 -0
  232. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/release_lock.lua +0 -0
  233. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_green.lua +0 -0
  234. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_red.lua +0 -0
  235. /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_yellow.lua +0 -0
@@ -10,69 +10,50 @@ module Stoplight
10
10
  # raising them or invoking a fallback if provided.
11
11
  #
12
12
  # @api private
13
- class YellowRunStrategy < RunStrategy
14
- # @!attribute [r] config
15
- # @return [Stoplight::Domain::Config] The configuration for the light.
16
- protected attr_reader :config
17
-
18
- # @!attribute [r] stare_store
19
- # @return [Stoplight::Domain::Storage::State]
20
- protected attr_reader :state_store
21
-
22
- # @!attribute [r] metrics_store
23
- # @return [Stoplight::Domain::Storage::Metrics]
24
- protected attr_reader :metrics_store
25
-
26
- # @!attribute [r] recovery_lock_store
27
- # @return [Stoplight::Domain::Storage::RecoveryLock]
28
- protected attr_reader :recovery_lock_store
29
-
30
- # @!attribute [r] notifiers
31
- # @return [Stoplight::Domain::StateTransitionNotifier]
32
- protected attr_reader :notifiers
33
-
34
- # @!attribute [r] request_tracker
35
- # @return [Stoplight::Domain::RecoveryProbeRequestRecorder]
36
- protected attr_reader :request_tracker
37
-
38
- # @!attribute [r] red_run_strategy
39
- # @return [Stoplight::Domain::Strategies::RedRunStrategy]
40
- protected attr_reader :red_run_strategy
41
-
42
- # @param config [Stoplight::Domain::Config]
43
- # @param notifiers [Array<Stoplight::Domain::StateTransitionNotifier>]
44
- # @param request_tracker [Stoplight::Domain::Tracker::RecoveryProbe]
45
- # @param red_run_strategy [Stoplight::Domain::Strategies::RedRunStrategy]
46
- # @param recovery_lock_store [Stoplight::Domain::Storage::RecoveryLock]
47
- def initialize(config:, notifiers:, request_tracker:, red_run_strategy:, state_store:, metrics_store:, recovery_lock_store:)
48
- @config = config
13
+ class YellowRunStrategy
14
+ def initialize(
15
+ name:,
16
+ error_tracking_policy:,
17
+ notifiers:,
18
+ request_tracker:,
19
+ red_run_strategy:,
20
+ state_store:,
21
+ metrics_store:,
22
+ recovery_lock_store:,
23
+ config: # FIXME: needed for backward compatibility, remove when notifier accepts light config
24
+ )
49
25
  @notifiers = notifiers
50
26
  @request_tracker = request_tracker
51
27
  @red_run_strategy = red_run_strategy
52
28
  @state_store = state_store
53
29
  @metrics_store = metrics_store
54
30
  @recovery_lock_store = recovery_lock_store
31
+ @name = name
32
+ @error_tracking_policy = error_tracking_policy
33
+ @config = config
55
34
  end
56
35
 
57
36
  # Executes the provided code block when the light is in the yellow state.
58
37
  #
59
- # @param fallback [Proc, nil] A fallback proc to execute in case of an error.
60
- # @param state_snapshot [Stoplight::Domain::StateSnapshot]
38
+ # @param fallback A fallback proc to execute in case of an error.
39
+ # @param state_snapshot
61
40
  # @yield The code block to execute.
62
- # @return [Object] The result of the code block if successful.
63
- # @raise [Exception] Re-raises the error if it is not tracked or no fallback is provided.
41
+ # @return The result of the code block if successful.
42
+ # @raise Re-raises the error if it is not tracked or no fallback is provided.
64
43
  def execute(fallback, state_snapshot:, &code)
65
44
  # Everything withing this block executed exclusively:
66
45
  # - enter recovery
67
46
  # - execute user's code
68
47
  # - record outcome
69
48
  # - transition to green or red if needed
70
- with_recovery_lock(fallback:, state_snapshot:) do
49
+ with_recovery_lock(fallback:, state_snapshot:, code:) do
71
50
  enter_recovery(state_snapshot)
72
51
 
73
- code.call.tap { record_recovery_probe_success }
52
+ result = code.call
53
+ record_recovery_probe_success
54
+ result
74
55
  rescue => error
75
- if config.track_error?(error)
56
+ if @error_tracking_policy.track?(error)
76
57
  record_recovery_probe_failure(error)
77
58
 
78
59
  if fallback
@@ -87,10 +68,19 @@ module Stoplight
87
68
  end
88
69
  end
89
70
 
90
- def with_recovery_lock(fallback:, state_snapshot:)
71
+ private
72
+
73
+ attr_reader :notifiers
74
+ attr_reader :request_tracker
75
+ attr_reader :red_run_strategy
76
+ attr_reader :state_store
77
+ attr_reader :metrics_store
78
+ attr_reader :recovery_lock_store
79
+
80
+ def with_recovery_lock(fallback:, state_snapshot:, code:)
91
81
  recovery_lock_token = recovery_lock_store.acquire_lock
92
82
  if recovery_lock_token.nil?
93
- return red_run_strategy.execute(fallback, state_snapshot:)
83
+ return red_run_strategy.execute(fallback, state_snapshot:, &code)
94
84
  end
95
85
 
96
86
  begin
@@ -100,23 +90,23 @@ module Stoplight
100
90
  end
101
91
  end
102
92
 
103
- private def record_recovery_probe_success
93
+ def record_recovery_probe_success
104
94
  request_tracker.record_success
105
95
  end
106
96
 
107
- private def record_recovery_probe_failure(error)
97
+ def record_recovery_probe_failure(error)
108
98
  request_tracker.record_failure(error)
109
99
  end
110
100
 
111
- # @param state_snapshot [Stoplight::Domain::StateSnapshot]
112
- # @return [void]
113
- private def enter_recovery(state_snapshot)
101
+ def enter_recovery(state_snapshot)
114
102
  return if state_snapshot.recovery_started?
115
103
 
116
104
  state_store.transition_to_color(Color::YELLOW)
117
105
  metrics_store.clear
106
+ # FIXME: use light config instead of @_config
107
+ # light_info = LightInfo.new(name: @name)
118
108
  notifiers.each do |notifier|
119
- notifier.notify(config, Color::RED, Color::YELLOW, nil)
109
+ notifier.notify(@config, Color::RED, Color::YELLOW, nil)
120
110
  end
121
111
  end
122
112
  end
@@ -3,32 +3,7 @@
3
3
  module Stoplight
4
4
  module Domain
5
5
  module Tracker
6
- class RecoveryProbe < Base
7
- # @!attribute [r] traffic_recovery
8
- # @return [Stoplight::Domain::TrafficRecovery::Base]
9
- protected attr_reader :traffic_recovery
10
-
11
- # @!attribute [r] traffic_control
12
- # @return [Stoplight::Domain::TrafficControl::Base]
13
- protected attr_reader :notifiers
14
-
15
- # @!attribute [r] config
16
- # @return [Stoplight::Domain::Config] The configuration for the light.
17
- protected attr_reader :config
18
-
19
- # @!attribute [r] metrics_store
20
- # @return [Stoplight::Domain::Storage::Metrics]
21
- protected attr_reader :metrics_store
22
-
23
- # @!attribute [r] state_store
24
- # @return [Stoplight::Domain::Storage::State]
25
- protected attr_reader :state_store
26
-
27
- # @param traffic_recovery [Stoplight::Domain::TrafficRecovery::Base]
28
- # @param notifiers [<Stoplight::Domain::StateTransitionNotifier>]
29
- # @param config [Stoplight::Domain::Config]
30
- # @param metrics_store [Stoplight::Domain::Storage::Metrics]
31
- # @param state_store [Stoplight::Domain::Storage::State]
6
+ class RecoveryProbe
32
7
  def initialize(traffic_recovery:, notifiers:, config:, metrics_store:, state_store:)
33
8
  @traffic_recovery = traffic_recovery
34
9
  @notifiers = notifiers
@@ -49,25 +24,33 @@ module Stoplight
49
24
 
50
25
  recover
51
26
  end
52
- RECOVERY_TRANSITIONS = {
53
- TrafficRecovery::GREEN => [Color::YELLOW, Color::GREEN],
54
- TrafficRecovery::RED => [Color::YELLOW, Color::RED]
55
- }.freeze
56
27
 
57
- private def recover
28
+ private
29
+
30
+ attr_reader :traffic_recovery
31
+ attr_reader :notifiers
32
+ attr_reader :config
33
+ attr_reader :metrics_store
34
+ attr_reader :state_store
35
+
36
+ def recover
58
37
  recovery_metrics = metrics_store.metrics_snapshot
59
38
  recovery_result = traffic_recovery.determine_color(config, recovery_metrics)
60
39
 
61
40
  return if recovery_result == TrafficRecovery::YELLOW
62
41
 
63
- from_color, to_color = RECOVERY_TRANSITIONS.fetch(recovery_result) do
42
+ from_color, to_color = case recovery_result
43
+ when TrafficRecovery::GREEN then [Color::YELLOW, Color::GREEN]
44
+ when TrafficRecovery::RED then [Color::YELLOW, Color::RED]
45
+ else
64
46
  raise "recovery strategy returned unexpected color: #{recovery_result}"
65
47
  end
66
48
 
67
49
  state_store.transition_to_color(to_color)
68
50
  metrics_store.clear
51
+ info = LightInfo.new(name: config.name)
69
52
  notifiers.each do |notifier|
70
- notifier.notify(config, from_color, to_color, nil)
53
+ notifier.notify(info, from_color, to_color, nil)
71
54
  end
72
55
  end
73
56
  end
@@ -9,32 +9,7 @@ module Stoplight
9
9
  # Used by +GreenRunStrategy+ to track failures and potentially open the circuit.
10
10
  #
11
11
  # @api private
12
- class Request < Base
13
- # @!attribute [r] traffic_control
14
- # @return [Stoplight::Domain::TrafficControl::Base]
15
- protected attr_reader :traffic_control
16
-
17
- # @!attribute [r] traffic_control
18
- # @return [Stoplight::Domain::TrafficControl::Base]
19
- protected attr_reader :notifiers
20
-
21
- # @!attribute [r] config
22
- # @return [Stoplight::Domain::Config] The configuration for the light.
23
- protected attr_reader :config
24
-
25
- # @!attribute metrics_store
26
- # @return [Stoplight::Storage::Metrics]
27
- protected attr_reader :metrics_store
28
-
29
- # @!attribute [r] state_store
30
- # @return [Stoplight::Domain::Storage::State]
31
- protected attr_reader :state_store
32
-
33
- # @param traffic_control [Stoplight::Domain::TrafficControl::Base]
34
- # @param notifiers [<Stoplight::Domain::StateTransitionNotifier>]
35
- # @param config [Stoplight::Domain::Config]
36
- # @param metrics_store [Stoplight::Storage::Metrics]
37
- # @param state_store [Stoplight::Domain::Storage::State]
12
+ class Request
38
13
  def initialize(traffic_control:, notifiers:, config:, metrics_store:, state_store:)
39
14
  @traffic_control = traffic_control
40
15
  @notifiers = notifiers
@@ -43,8 +18,6 @@ module Stoplight
43
18
  @state_store = state_store
44
19
  end
45
20
 
46
- # @param exception [Exception]
47
- # @return [void]
48
21
  def record_failure(exception)
49
22
  metrics_store.record_failure(exception)
50
23
  metrics = metrics_store.metrics_snapshot
@@ -52,16 +25,24 @@ module Stoplight
52
25
  transition_to_red(exception, metrics:)
53
26
  end
54
27
 
55
- # @return [void]
56
28
  def record_success = metrics_store.record_success
57
29
 
58
- private def transition_to_red(exception, metrics:)
30
+ private
31
+
32
+ attr_reader :traffic_control
33
+ attr_reader :notifiers
34
+ attr_reader :config
35
+ attr_reader :metrics_store
36
+ attr_reader :state_store
37
+
38
+ def transition_to_red(exception, metrics:)
59
39
  if traffic_control.stop_traffic?(config, metrics)
60
40
  # Returns true only if not yet in red therefore preventing
61
41
  # duplicate notifications
62
42
  if state_store.transition_to_color(Color::RED)
43
+ info = LightInfo.new(name: config.name)
63
44
  notifiers.each do |notifier|
64
- notifier.notify(config, Color::GREEN, Color::RED, exception)
45
+ notifier.notify(info, Color::GREEN, Color::RED, exception)
65
46
  end
66
47
  end
67
48
  end
@@ -26,27 +26,24 @@ module Stoplight
26
26
  #
27
27
  # Will switch to red only if 5 consecutive failures occur regardless of the time window
28
28
  # @api private
29
- class ConsecutiveErrors < Base
30
- # @param config [Stoplight::Domain::Config]
31
- # @return [Stoplight::Domain::CompatibilityResult]
29
+ class ConsecutiveErrors
32
30
  def check_compatibility(config)
33
31
  if config.threshold <= 0
34
- incompatible("`threshold` should be bigger than 0")
32
+ CompatibilityResult.incompatible("`threshold` should be bigger than 0")
35
33
  elsif !config.threshold.is_a?(Integer)
36
- incompatible("`threshold` should be an integer")
34
+ CompatibilityResult.incompatible("`threshold` should be an integer")
37
35
  else
38
- compatible
36
+ CompatibilityResult.compatible
39
37
  end
40
38
  end
41
39
 
42
- # Determines if traffic should be stopped based on failure counts.
43
- #
44
- # @param config [Stoplight::Domain::Config]
45
- # @param metrics [Stoplight::Domain::Metrics]
46
- # @return [Boolean] true if failures have reached the threshold, false otherwise
47
40
  def stop_traffic?(config, metrics)
48
41
  metrics.consecutive_errors >= config.threshold
49
42
  end
43
+
44
+ def ==(other)
45
+ other.is_a?(self.class)
46
+ end
50
47
  end
51
48
  end
52
49
  end
@@ -16,35 +16,39 @@ module Stoplight
16
16
  # traffic_control = Stoplight::Domain::TrafficControl::ErrorRate.new(min_requests: 100)
17
17
  #
18
18
  # @api private
19
- class ErrorRate < Base
20
- # @!attribute min_requests
21
- # @return [Integer]
22
- attr_reader :min_requests
23
-
24
- # @param min_requests [Integer] Minimum number of requests before traffic control is applied.
19
+ class ErrorRate
20
+ # @param min_requests Minimum number of requests before traffic control is applied.
25
21
  # until this number of requests is reached, the error rate will not be considered.
26
22
  def initialize(min_requests: 10)
27
23
  @min_requests = min_requests
28
24
  end
29
25
 
30
- # @param config [Stoplight::Domain::Config]
31
- # @return [Stoplight::Domain::CompatibilityResult]
32
26
  def check_compatibility(config)
33
27
  if config.window_size.nil?
34
- incompatible("`window_size` should be set")
28
+ CompatibilityResult.incompatible("`window_size` should be set")
35
29
  elsif config.threshold < 0 || config.threshold > 1
36
- incompatible("`threshold` should be between 0 and 1")
30
+ CompatibilityResult.incompatible("`threshold` should be between 0 and 1")
37
31
  else
38
- compatible
32
+ CompatibilityResult.compatible
39
33
  end
40
34
  end
41
35
 
42
- # @param config [Stoplight::Domain::Config]
43
- # @param metrics [Stoplight::Domain::Metrics]
44
- # @return [Boolean]
45
36
  def stop_traffic?(config, metrics)
46
- metrics.requests >= min_requests && metrics.error_rate >= config.threshold
37
+ error_rate = metrics.error_rate
38
+ requests = metrics.requests
39
+
40
+ raise ArgumentError, "accepts only windowed metrics" if error_rate.nil? || requests.nil?
41
+
42
+ requests >= min_requests && error_rate >= config.threshold
47
43
  end
44
+
45
+ def ==(other)
46
+ other.is_a?(self.class) && min_requests == other.min_requests
47
+ end
48
+
49
+ protected
50
+
51
+ attr_reader :min_requests
48
52
  end
49
53
  end
50
54
  end
@@ -33,24 +33,18 @@ module Stoplight
33
33
  # conservative approach prioritizes stability over recovery speed.
34
34
  #
35
35
  # @api private
36
- class ConsecutiveSuccesses < Base
37
- # @param config [Stoplight::Domain::Config]
38
- # @return [Stoplight::Domain::CompatibilityResult]
36
+ class ConsecutiveSuccesses
39
37
  def check_compatibility(config)
40
38
  if config.recovery_threshold <= 0
41
- incompatible("`recovery_threshold` should be bigger than 0")
39
+ CompatibilityResult.incompatible("`recovery_threshold` should be bigger than 0")
42
40
  elsif !config.recovery_threshold.is_a?(Integer)
43
- incompatible("`recovery_threshold` should be an integer")
41
+ CompatibilityResult.incompatible("`recovery_threshold` should be an integer")
44
42
  else
45
- compatible
43
+ CompatibilityResult.compatible
46
44
  end
47
45
  end
48
46
 
49
47
  # Determines if traffic should be resumed based on successes counts.
50
- #
51
- # @param config [Stoplight::Domain::Config]
52
- # @param recovery_metrics [Stoplight::Domain::Metrics]
53
- # @return [TrafficRecovery::Decision]
54
48
  def determine_color(config, recovery_metrics)
55
49
  if recovery_metrics.consecutive_errors > 0
56
50
  TrafficRecovery::RED
@@ -60,6 +54,8 @@ module Stoplight
60
54
  TrafficRecovery::YELLOW
61
55
  end
62
56
  end
57
+
58
+ def ==(other) = other.is_a?(self.class)
63
59
  end
64
60
  end
65
61
  end
@@ -3,10 +3,9 @@
3
3
  module Stoplight
4
4
  module Domain
5
5
  module TrafficRecovery
6
- Decision = Data.define(:decision)
7
- GREEN = Decision.new("green")
8
- YELLOW = Decision.new("yellow")
9
- RED = Decision.new("red")
6
+ GREEN = :green
7
+ YELLOW = :yellow
8
+ RED = :red
10
9
  end
11
10
  end
12
11
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Stoplight
4
+ module Error
5
+ class Base < StandardError
6
+ end
7
+
8
+ class ConfigurationError < Base
9
+ end
10
+
11
+ class IncorrectColor < Base
12
+ end
13
+
14
+ class RedLight < Base
15
+ # @!attribute light_name
16
+ # @return [String] The light's name
17
+ attr_reader :light_name
18
+
19
+ # @!attribute cool_off_time
20
+ # @return [Numeric] Cool-off period in seconds
21
+ attr_reader :cool_off_time
22
+
23
+ # @!attribute retry_after
24
+ # @return [Time | nil] Absolute Time after which a recovery attempt can occur
25
+ # could be nil if the light is locked red
26
+ attr_reader :retry_after
27
+
28
+ # Initializes a new RedLight error.
29
+ #
30
+ # @param light_name [String] The light's name
31
+ #
32
+ # @option cool_off_time [Numeric] Cool-off period in seconds
33
+ #
34
+ # @option retry_after [Time | nil] Absolute Time after which a recovery attempt can occur
35
+ #
36
+ # @return [Stoplight::Error::RedLight]
37
+ def initialize(light_name, cool_off_time:, retry_after:)
38
+ @light_name = light_name
39
+ @cool_off_time = cool_off_time
40
+ @retry_after = retry_after
41
+
42
+ super(light_name)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -4,33 +4,22 @@ require "securerandom"
4
4
 
5
5
  module Stoplight
6
6
  module Infrastructure
7
- module DataStore
7
+ module FailSafe
8
8
  # A wrapper around a data store that provides fail-safe mechanisms using a
9
9
  # circuit breaker. It ensures that operations on the data store can gracefully
10
10
  # handle failures by falling back to default values when necessary.
11
11
  #
12
12
  # @api private
13
- class FailSafe < Domain::DataStore
14
- # @!attribute data_store
15
- # @return [Stoplight::DataStore::Base] The underlying primary data store being used
13
+ # steep:ignore:start
14
+ class DataStore
15
+ # The underlying primary data store being used
16
16
  attr_reader :data_store
17
-
18
- # @!attribute error_notifier
19
- # @return [Proc]
20
17
  attr_reader :error_notifier
21
-
22
- # @!attribute failover_data_store
23
- # @return [Stoplight::DataStore::Base] The fallback data store used when the primary fails.
18
+ # The fallback data store used when the primary fails.
24
19
  attr_reader :failover_data_store
25
-
26
- # @!attribute circuit_breaker
27
- # @return [Stoplight::Light] The circuit breaker used to handle data store failures.
20
+ # The circuit breaker used to handle data store failures.
28
21
  private attr_reader :circuit_breaker
29
22
 
30
- # @param data_store [Stoplight::Domain::DataStore]
31
- # @param error_notifier [Proc]
32
- # @param failover_data_store [Stoplight::Domain::DataStore]
33
- # @param circuit_breaker [Stoplight::Domain::Light]
34
23
  def initialize(data_store:, error_notifier:, failover_data_store:, circuit_breaker:)
35
24
  @data_store = data_store
36
25
  @error_notifier = error_notifier
@@ -44,15 +33,15 @@ module Stoplight
44
33
  end
45
34
  end
46
35
 
47
- def get_metrics(config, *args, **kwargs)
48
- with_fallback(:get_metrics, config, *args, **kwargs) do
49
- data_store.get_metrics(config, *args, **kwargs)
36
+ def get_metrics(config)
37
+ with_fallback(:get_metrics, config) do
38
+ data_store.get_metrics(config)
50
39
  end
51
40
  end
52
41
 
53
- def get_recovery_metrics(config, *args, **kwargs)
54
- with_fallback(:get_recovery_metrics, config, *args, **kwargs) do
55
- data_store.get_recovery_metrics(config, *args, **kwargs)
42
+ def get_recovery_metrics(config)
43
+ with_fallback(:get_recovery_metrics, config) do
44
+ data_store.get_recovery_metrics(config)
56
45
  end
57
46
  end
58
47
 
@@ -74,45 +63,45 @@ module Stoplight
74
63
  end
75
64
  end
76
65
 
77
- def record_failure(config, *args, **kwargs)
78
- with_fallback(:record_failure, config, *args, **kwargs) do
79
- data_store.record_failure(config, *args, **kwargs)
66
+ def record_failure(config, exception)
67
+ with_fallback(:record_failure, config, exception) do
68
+ data_store.record_failure(config, exception)
80
69
  end
81
70
  end
82
71
 
83
- def record_success(config, *args, **kwargs)
84
- with_fallback(:record_success, config, *args, **kwargs) do
85
- data_store.record_success(config, *args, **kwargs)
72
+ def record_success(config)
73
+ with_fallback(:record_success, config) do
74
+ data_store.record_success(config)
86
75
  end
87
76
  end
88
77
 
89
- def record_recovery_probe_success(config, *args, **kwargs)
90
- with_fallback(:record_recovery_probe_success, config, *args, **kwargs) do
91
- data_store.record_recovery_probe_success(config, *args, **kwargs)
78
+ def record_recovery_probe_success(config)
79
+ with_fallback(:record_recovery_probe_success, config) do
80
+ data_store.record_recovery_probe_success(config)
92
81
  end
93
82
  end
94
83
 
95
- def record_recovery_probe_failure(config, *args, **kwargs)
96
- with_fallback(:record_recovery_probe_failure, config, *args, **kwargs) do
97
- data_store.record_recovery_probe_failure(config, *args, **kwargs)
84
+ def record_recovery_probe_failure(config, exception)
85
+ with_fallback(:record_recovery_probe_failure, config, exception) do
86
+ data_store.record_recovery_probe_failure(config, exception)
98
87
  end
99
88
  end
100
89
 
101
- def set_state(config, *args, **kwargs)
102
- with_fallback(:set_state, config, *args, **kwargs) do
103
- data_store.set_state(config, *args, **kwargs)
90
+ def set_state(config, state)
91
+ with_fallback(:set_state, config, state) do
92
+ data_store.set_state(config, state)
104
93
  end
105
94
  end
106
95
 
107
- def transition_to_color(config, *args, **kwargs)
108
- with_fallback(:transition_to_color, config, *args, **kwargs) do
109
- data_store.transition_to_color(config, *args, **kwargs)
96
+ def transition_to_color(config, color)
97
+ with_fallback(:transition_to_color, config, color) do
98
+ data_store.transition_to_color(config, color)
110
99
  end
111
100
  end
112
101
 
113
- def delete_light(config, *args, **kwargs)
114
- with_fallback(:delete_light, config, *args, **kwargs) do
115
- data_store.delete_light(config, *args, **kwargs)
102
+ def delete_light(config)
103
+ with_fallback(:delete_light, config) do
104
+ data_store.delete_light(config)
116
105
  end
117
106
  end
118
107
 
@@ -127,10 +116,9 @@ module Stoplight
127
116
  # Redis tokens release via primary (with error notification on failure).
128
117
  # Memory tokens release via failover directly.
129
118
  #
130
- # @param recovery_lock_token [Stoplight::Domain::RecoveryLockToken]
131
119
  def release_recovery_lock(recovery_lock_token)
132
120
  case recovery_lock_token
133
- in Redis::RecoveryLockToken
121
+ in Redis::DataStore::RecoveryLockToken
134
122
  fallback = proc do |error|
135
123
  error_notifier.call(error) if error
136
124
  end
@@ -138,7 +126,7 @@ module Stoplight
138
126
  circuit_breaker.run(fallback) do
139
127
  data_store.release_recovery_lock(recovery_lock_token)
140
128
  end
141
- in Memory::RecoveryLockToken
129
+ in Memory::DataStore::RecoveryLockToken
142
130
  failover_data_store.release_recovery_lock(recovery_lock_token)
143
131
  end
144
132
  end
@@ -148,17 +136,17 @@ module Stoplight
148
136
  other.failover_data_store == failover_data_store
149
137
  end
150
138
 
151
- # @param method_name [Symbol] protected method name
152
139
  private def with_fallback(method_name, *args, **kwargs, &code)
153
- fallback = proc do |error|
140
+ fallback = ->(error) {
154
141
  config = args.first
155
142
  error_notifier.call(error) if config && error
156
- @failover_data_store.public_send(method_name, *args, **kwargs)
157
- end
143
+ failover_data_store.public_send(method_name, *args, **kwargs)
144
+ }
158
145
 
159
146
  circuit_breaker.run(fallback, &code)
160
147
  end
161
148
  end
149
+ # steep:ignore:end
162
150
  end
163
151
  end
164
152
  end