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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/UPGRADING.md +303 -0
- data/lib/generators/stoplight/install/install_generator.rb +6 -1
- data/lib/stoplight/admin/dependencies.rb +1 -1
- data/lib/stoplight/admin/helpers.rb +20 -4
- data/lib/stoplight/admin/lights_repository/light.rb +22 -6
- data/lib/stoplight/admin/lights_repository.rb +6 -5
- data/lib/stoplight/admin/views/_card.erb +8 -5
- data/lib/stoplight/color.rb +9 -0
- data/lib/stoplight/data_store.rb +28 -0
- data/lib/stoplight/domain/compatibility_result.rb +7 -7
- data/lib/stoplight/domain/config.rb +38 -39
- data/lib/stoplight/domain/error_tracking_policy.rb +27 -0
- data/lib/stoplight/domain/failure.rb +1 -1
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +2 -0
- data/lib/stoplight/domain/light.rb +15 -46
- data/lib/stoplight/domain/light_info.rb +7 -0
- data/lib/stoplight/domain/metrics_snapshot.rb +58 -0
- data/lib/stoplight/domain/state_snapshot.rb +29 -23
- data/lib/stoplight/domain/storage/recovery_lock_token.rb +15 -0
- data/lib/stoplight/domain/strategies/green_run_strategy.rb +18 -26
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +9 -12
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +41 -51
- data/lib/stoplight/domain/tracker/recovery_probe.rb +16 -33
- data/lib/stoplight/domain/tracker/request.rb +12 -31
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +8 -11
- data/lib/stoplight/domain/traffic_control/error_rate.rb +19 -15
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +6 -10
- data/lib/stoplight/domain/traffic_recovery.rb +3 -4
- data/lib/stoplight/error.rb +46 -0
- data/lib/stoplight/infrastructure/{data_store/fail_safe.rb → fail_safe/data_store.rb} +39 -51
- data/lib/stoplight/infrastructure/fail_safe/storage/metrics.rb +65 -0
- data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock.rb +69 -0
- data/lib/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rb +19 -0
- data/lib/stoplight/infrastructure/fail_safe/storage/state.rb +62 -0
- data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/metrics.rb +2 -2
- data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_store.rb +10 -12
- data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_token.rb +3 -6
- data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/sliding_window.rb +21 -26
- data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/state.rb +3 -3
- data/lib/stoplight/infrastructure/{data_store/memory.rb → memory/data_store.rb} +36 -32
- data/lib/stoplight/infrastructure/memory/storage/recovery_lock.rb +35 -0
- data/lib/stoplight/infrastructure/memory/storage/recovery_metrics.rb +16 -0
- data/lib/stoplight/infrastructure/memory/storage/state.rb +155 -0
- data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +103 -0
- data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +101 -0
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +9 -21
- data/lib/stoplight/infrastructure/notifier/generic.rb +4 -14
- data/lib/stoplight/infrastructure/notifier/io.rb +1 -2
- data/lib/stoplight/infrastructure/notifier/logger.rb +1 -2
- data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_store.rb +9 -22
- data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_token.rb +7 -14
- data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/scripting.rb +22 -20
- data/lib/stoplight/infrastructure/{data_store/redis.rb → redis/data_store.rb} +48 -56
- data/lib/stoplight/infrastructure/redis/storage/key_space.rb +51 -0
- data/lib/stoplight/infrastructure/redis/storage/metrics.rb +40 -0
- data/lib/stoplight/infrastructure/redis/storage/recovery_lock/release_lock.lua +6 -0
- data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +64 -0
- data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +20 -0
- data/lib/stoplight/infrastructure/redis/storage/scripting.rb +18 -0
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +10 -0
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +10 -0
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +9 -0
- data/lib/stoplight/infrastructure/redis/storage/state.rb +141 -0
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +28 -0
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +26 -0
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +123 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +26 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +36 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +35 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +174 -0
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +3 -10
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +8 -11
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +6 -14
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +6 -17
- data/lib/stoplight/infrastructure/system_clock.rb +16 -0
- data/lib/stoplight/notifier.rb +11 -0
- data/lib/stoplight/state.rb +9 -0
- data/lib/stoplight/types.rb +29 -0
- data/lib/stoplight/undefined.rb +16 -0
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/config_compatibility_validator.rb +54 -0
- data/lib/stoplight/wiring/configuration_dsl.rb +101 -0
- data/lib/stoplight/wiring/data_store_backend.rb +26 -0
- data/lib/stoplight/wiring/default.rb +1 -1
- data/lib/stoplight/wiring/default_config.rb +21 -0
- data/lib/stoplight/wiring/default_configuration.rb +70 -53
- data/lib/stoplight/wiring/light_builder.rb +76 -63
- data/lib/stoplight/wiring/light_factory/traffic_control_dsl.rb +3 -3
- data/lib/stoplight/wiring/light_factory/traffic_recovery_dsl.rb +4 -4
- data/lib/stoplight/wiring/light_factory.rb +78 -52
- data/lib/stoplight/wiring/memory/backend.rb +57 -0
- data/lib/stoplight/wiring/redis/backend.rb +116 -0
- data/lib/stoplight/wiring/storage_set.rb +12 -0
- data/lib/stoplight/wiring/storage_set_builder.rb +51 -0
- data/lib/stoplight/wiring/system/light_builder.rb +47 -0
- data/lib/stoplight/wiring/system/light_factory.rb +64 -0
- data/lib/stoplight/wiring/system.rb +129 -0
- data/lib/stoplight.rb +196 -25
- data/sig/_private/generators/stoplight/install/install_generator.rbs +22 -0
- data/sig/_private/stoplight/common/deprecations.rbs +9 -0
- data/sig/_private/stoplight/data_store.rbs +6 -0
- data/sig/_private/stoplight/domain/compatibility_result.rbs +18 -0
- data/sig/_private/stoplight/domain/config.rbs +65 -0
- data/sig/_private/stoplight/domain/error_tracking_policy.rbs +14 -0
- data/sig/_private/stoplight/domain/failure.rbs +16 -0
- data/sig/_private/stoplight/domain/light.rbs +25 -0
- data/sig/_private/stoplight/domain/light_info.rbs +19 -0
- data/sig/_private/stoplight/domain/metrics_snapshot.rbs +38 -0
- data/sig/_private/stoplight/domain/ports/clock.rbs +18 -0
- data/sig/_private/stoplight/domain/ports/data_store.rbs +76 -0
- data/{lib/stoplight/domain/light_factory.rb → sig/_private/stoplight/domain/ports/light_factory.rbs} +33 -28
- data/sig/_private/stoplight/domain/ports/metrics_store.rbs +29 -0
- data/sig/_private/stoplight/domain/ports/recovery_lock_store.rbs +52 -0
- data/sig/_private/stoplight/domain/ports/recovery_lock_token.rbs +6 -0
- data/sig/_private/stoplight/domain/ports/run_strategy.rbs +14 -0
- data/sig/_private/stoplight/domain/ports/state_store.rbs +79 -0
- data/sig/_private/stoplight/domain/ports/traffic_control.rbs +41 -0
- data/sig/_private/stoplight/domain/ports/traffic_recovery.rbs +47 -0
- data/sig/_private/stoplight/domain/state_snapshot.rbs +32 -0
- data/sig/_private/stoplight/domain/storage/recovery_lock_token.rbs +11 -0
- data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +17 -0
- data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +17 -0
- data/sig/_private/stoplight/domain/strategies/yellow_run_strategy.rbs +42 -0
- data/{lib/stoplight/domain/tracker/base.rb → sig/_private/stoplight/domain/tracker/base.rbs} +0 -4
- data/sig/_private/stoplight/domain/tracker/recovery_probe.rbs +25 -0
- data/sig/_private/stoplight/domain/tracker/request.rbs +26 -0
- data/sig/_private/stoplight/domain/traffic_control/consecutive_errors.rbs +9 -0
- data/sig/_private/stoplight/domain/traffic_control/error_rate.rbs +13 -0
- data/sig/_private/stoplight/domain/traffic_recovery/consecutive_successes.rbs +9 -0
- data/sig/_private/stoplight/domain/traffic_recovery.rbs +9 -0
- data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +26 -0
- data/sig/_private/stoplight/infrastructure/fail_safe/storage/metrics.rbs +25 -0
- data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock.rbs +29 -0
- data/sig/_private/stoplight/infrastructure/fail_safe/storage/recovery_lock_token.rbs +19 -0
- data/sig/_private/stoplight/infrastructure/fail_safe/storage/state.rbs +25 -0
- data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +25 -0
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +19 -0
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +17 -0
- data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +27 -0
- data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +17 -0
- data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +30 -0
- data/sig/_private/stoplight/infrastructure/memory/storage/recovery_lock.rbs +15 -0
- data/sig/_private/stoplight/infrastructure/memory/storage/recovery_metrics.rbs +10 -0
- data/sig/_private/stoplight/infrastructure/memory/storage/state.rbs +28 -0
- data/sig/_private/stoplight/infrastructure/memory/storage/unbounded_metrics.rbs +25 -0
- data/sig/_private/stoplight/infrastructure/memory/storage/window_metrics.rbs +26 -0
- data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +17 -0
- data/sig/_private/stoplight/infrastructure/notifier/generic.rbs +18 -0
- data/sig/_private/stoplight/infrastructure/notifier/io.rbs +14 -0
- data/sig/_private/stoplight/infrastructure/notifier/logger.rbs +14 -0
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +24 -0
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +21 -0
- data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +34 -0
- data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +67 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +19 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/metrics.rbs +17 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/recovery_lock.rbs +26 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/recovery_metrics.rbs +10 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +13 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/state.rbs +32 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/unbounded_metrics.rbs +21 -0
- data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +34 -0
- data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +17 -0
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +13 -0
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +14 -0
- data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +14 -0
- data/sig/_private/stoplight/infrastructure/system_clock.rbs +7 -0
- data/sig/_private/stoplight/system/light_builder.rbs +23 -0
- data/sig/_private/stoplight/system/light_factory.rbs +17 -0
- data/sig/_private/stoplight/types.rbs +6 -0
- data/sig/_private/stoplight/wiring/config_compatibility_validator.rbs +19 -0
- data/sig/_private/stoplight/wiring/configuration_dsl.rbs +43 -0
- data/sig/_private/stoplight/wiring/data_store_backend.rbs +11 -0
- data/sig/_private/stoplight/wiring/default.rbs +26 -0
- data/{lib/stoplight/wiring/data_store/memory.rb → sig/_private/stoplight/wiring/default_config.rbs} +1 -4
- data/sig/_private/stoplight/wiring/default_configuration.rbs +29 -0
- data/sig/_private/stoplight/wiring/light_builder.rbs +48 -0
- data/sig/_private/stoplight/wiring/light_factory/traffic_control_dsl.rbs +7 -0
- data/sig/_private/stoplight/wiring/light_factory/traffic_recovery_dsl.rbs +7 -0
- data/sig/_private/stoplight/wiring/light_factory.rbs +16 -0
- data/sig/_private/stoplight/wiring/memory/backend.rbs +26 -0
- data/sig/_private/stoplight/wiring/notifier_factory.rbs +10 -0
- data/sig/_private/stoplight/wiring/redis/backend.rbs +38 -0
- data/sig/_private/stoplight/wiring/storage_set.rbs +38 -0
- data/sig/_private/stoplight/wiring/storage_set_builder.rbs +15 -0
- data/sig/_private/stoplight/wiring/system.rbs +15 -0
- data/sig/_private/stoplight.rbs +48 -0
- data/sig/stoplight/color.rbs +7 -0
- data/sig/stoplight/data_store.rbs +19 -0
- data/sig/stoplight/error.rbs +20 -0
- data/sig/stoplight/notifier.rbs +11 -0
- data/sig/stoplight/ports/configuration.rbs +19 -0
- data/sig/stoplight/ports/exception_matcher.rbs +8 -0
- data/sig/stoplight/ports/light.rbs +12 -0
- data/sig/stoplight/ports/light_info.rbs +5 -0
- data/sig/stoplight/ports/state_transition_notifier.rbs +15 -0
- data/sig/stoplight/ports/system.rbs +21 -0
- data/sig/stoplight/state.rbs +7 -0
- data/sig/stoplight/undefined.rbs +9 -0
- data/sig/stoplight/version.rbs +3 -0
- data/sig/stoplight.rbs +66 -0
- metadata +175 -47
- data/lib/stoplight/domain/color.rb +0 -11
- data/lib/stoplight/domain/data_store.rb +0 -146
- data/lib/stoplight/domain/error.rb +0 -42
- data/lib/stoplight/domain/metrics.rb +0 -64
- data/lib/stoplight/domain/recovery_lock_token.rb +0 -15
- data/lib/stoplight/domain/state.rb +0 -11
- data/lib/stoplight/domain/state_transition_notifier.rb +0 -25
- data/lib/stoplight/domain/storage/metrics.rb +0 -42
- data/lib/stoplight/domain/storage/recovery_lock.rb +0 -56
- data/lib/stoplight/domain/storage/state.rb +0 -87
- data/lib/stoplight/domain/strategies/run_strategy.rb +0 -22
- data/lib/stoplight/domain/traffic_control/base.rb +0 -74
- data/lib/stoplight/domain/traffic_recovery/base.rb +0 -79
- data/lib/stoplight/wiring/data_store/base.rb +0 -11
- data/lib/stoplight/wiring/data_store/redis.rb +0 -25
- data/lib/stoplight/wiring/default_factory_builder.rb +0 -25
- data/lib/stoplight/wiring/light/default_config.rb +0 -18
- data/lib/stoplight/wiring/light/system_config.rb +0 -11
- data/lib/stoplight/wiring/light_factory/compatibility_validator.rb +0 -55
- data/lib/stoplight/wiring/light_factory/config_normalizer.rb +0 -71
- data/lib/stoplight/wiring/light_factory/configuration_pipeline.rb +0 -72
- data/lib/stoplight/wiring/public_api.rb +0 -29
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/get_metrics.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_failure.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_failure.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_recovery_probe_success.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/record_success.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/release_lock.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_green.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_red.lua +0 -0
- /data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/lua_scripts/transition_to_yellow.lua +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Memory
|
|
6
|
+
module Storage
|
|
7
|
+
# Thread-safe in-memory state storage for a single light.
|
|
8
|
+
#
|
|
9
|
+
# Manages light state transitions and ensures notification
|
|
10
|
+
# deduplication through atomic transition detection. Each transition
|
|
11
|
+
# method returns whether the calling thread was first to trigger that
|
|
12
|
+
# transition, enabling exactly-once notification semantics.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# state = State.new(clock: SystemClock.new, cool_off_time: 60)
|
|
16
|
+
#
|
|
17
|
+
# # Multiple threads may call this concurrently
|
|
18
|
+
# if state.transition_to_color(Color::RED)
|
|
19
|
+
# # Only one thread reaches here - send notification
|
|
20
|
+
# notifier.notify(circuit_name, :opened)
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# @example Inspecting current state
|
|
24
|
+
# snapshot = state.state_snapshot
|
|
25
|
+
# snapshot.locked_state # => "unlocked"
|
|
26
|
+
# snapshot.breached_at # => 2025-01-15 10:30:00 UTC
|
|
27
|
+
# snapshot.recovery_scheduled_after # => 2025-01-15 10:31:00 UTC
|
|
28
|
+
#
|
|
29
|
+
# @see Stoplight::Domain::StateSnapshot for the structure of state snapshots
|
|
30
|
+
#
|
|
31
|
+
class State
|
|
32
|
+
def initialize(clock:, cool_off_time:)
|
|
33
|
+
@locked_state = Stoplight::State::UNLOCKED
|
|
34
|
+
@mutex = Thread::Mutex.new
|
|
35
|
+
@clock = clock
|
|
36
|
+
@cool_off_time = cool_off_time
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def set_state(state)
|
|
40
|
+
mutex.synchronize do
|
|
41
|
+
self.locked_state = state
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def state_snapshot
|
|
46
|
+
mutex.synchronize do
|
|
47
|
+
Domain::StateSnapshot.new(
|
|
48
|
+
time: clock.current_time,
|
|
49
|
+
locked_state:,
|
|
50
|
+
recovery_scheduled_after:,
|
|
51
|
+
recovery_started_at:,
|
|
52
|
+
breached_at:
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Combined method that performs the state transition based on color
|
|
58
|
+
#
|
|
59
|
+
# @param color The color to transition to ("GREEN", "YELLOW", or "RED")
|
|
60
|
+
# @return true if this is the first instance to detect this transition
|
|
61
|
+
def transition_to_color(color)
|
|
62
|
+
case color
|
|
63
|
+
when Color::GREEN
|
|
64
|
+
transition_to_green
|
|
65
|
+
when Color::YELLOW
|
|
66
|
+
transition_to_yellow
|
|
67
|
+
when Color::RED
|
|
68
|
+
transition_to_red
|
|
69
|
+
else
|
|
70
|
+
raise ArgumentError, "Invalid color: #{color}"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def clear
|
|
75
|
+
mutex.synchronize do
|
|
76
|
+
self.locked_state = Stoplight::State::UNLOCKED
|
|
77
|
+
self.recovered_at = nil
|
|
78
|
+
self.recovery_scheduled_after = nil
|
|
79
|
+
self.breached_at = nil
|
|
80
|
+
self.recovery_started_at = nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
attr_accessor :locked_state
|
|
87
|
+
attr_accessor :recovered_at
|
|
88
|
+
attr_accessor :recovery_scheduled_after
|
|
89
|
+
attr_accessor :recovery_started_at
|
|
90
|
+
attr_accessor :breached_at
|
|
91
|
+
attr_reader :mutex
|
|
92
|
+
attr_reader :clock
|
|
93
|
+
attr_reader :cool_off_time
|
|
94
|
+
|
|
95
|
+
# Transitions to GREEN state and ensures only one notification
|
|
96
|
+
#
|
|
97
|
+
# @return true if this is the first instance to detect this transition
|
|
98
|
+
def transition_to_green
|
|
99
|
+
mutex.synchronize do
|
|
100
|
+
if recovered_at
|
|
101
|
+
false
|
|
102
|
+
else
|
|
103
|
+
self.recovered_at = clock.current_time
|
|
104
|
+
self.recovery_started_at = nil
|
|
105
|
+
self.breached_at = nil
|
|
106
|
+
self.recovery_scheduled_after = nil
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Transitions to YELLOW (recovery) state and ensures only one notification
|
|
113
|
+
#
|
|
114
|
+
# @return true if this is the first instance to detect this transition
|
|
115
|
+
def transition_to_yellow
|
|
116
|
+
mutex.synchronize do
|
|
117
|
+
if recovery_started_at.nil?
|
|
118
|
+
self.recovery_started_at = clock.current_time
|
|
119
|
+
self.recovery_scheduled_after = nil
|
|
120
|
+
self.recovered_at = nil
|
|
121
|
+
self.breached_at = nil
|
|
122
|
+
true
|
|
123
|
+
else
|
|
124
|
+
self.recovery_scheduled_after = nil
|
|
125
|
+
self.recovered_at = nil
|
|
126
|
+
self.breached_at = nil
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Transitions to RED state and ensures only one notification
|
|
133
|
+
#
|
|
134
|
+
# @return true if this is the first instance to detect this transition
|
|
135
|
+
def transition_to_red
|
|
136
|
+
mutex.synchronize do
|
|
137
|
+
current_time = clock.current_time
|
|
138
|
+
|
|
139
|
+
self.recovery_scheduled_after = current_time + cool_off_time
|
|
140
|
+
self.recovery_started_at = nil
|
|
141
|
+
self.recovered_at = nil
|
|
142
|
+
|
|
143
|
+
if breached_at
|
|
144
|
+
false
|
|
145
|
+
else
|
|
146
|
+
self.breached_at = current_time
|
|
147
|
+
true
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Memory
|
|
6
|
+
module Storage
|
|
7
|
+
# Thread-safe metrics storage for consecutive-error light strategies.
|
|
8
|
+
#
|
|
9
|
+
# Unlike +WindowMetrics+, this class does not track event counts within
|
|
10
|
+
# a time window. It only maintains:
|
|
11
|
+
# - Consecutive success/failure counters (reset on opposite outcome)
|
|
12
|
+
# - Most recent error with timestamp
|
|
13
|
+
# - Most recent success timestamp
|
|
14
|
+
#
|
|
15
|
+
# This is appropriate for circuit breakers using threshold-based strategies
|
|
16
|
+
# (e.g., "open after 5 consecutive failures") rather than rate-based
|
|
17
|
+
# strategies (e.g., "open when error rate exceeds 50%").
|
|
18
|
+
#
|
|
19
|
+
# @note The +#errors+ and +#successes+ fields in the returned +Stoplight::Domain::Metrics+
|
|
20
|
+
# are always +nil+ since totals aren't tracked.
|
|
21
|
+
#
|
|
22
|
+
class UnboundedMetrics
|
|
23
|
+
def initialize(clock:)
|
|
24
|
+
initialize_metrics
|
|
25
|
+
@clock = clock
|
|
26
|
+
@mutex = Mutex.new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get metrics for the current light
|
|
30
|
+
def metrics_snapshot
|
|
31
|
+
mutex.synchronize do
|
|
32
|
+
Domain::MetricsSnapshot.new(
|
|
33
|
+
errors: nil,
|
|
34
|
+
successes: nil,
|
|
35
|
+
consecutive_errors: consecutive_errors.to_i,
|
|
36
|
+
consecutive_successes: consecutive_successes.to_i,
|
|
37
|
+
last_error: last_error,
|
|
38
|
+
last_success_at: last_success_at
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Records successful circuit breaker execution
|
|
44
|
+
def record_success
|
|
45
|
+
current_time = clock.current_time
|
|
46
|
+
|
|
47
|
+
mutex.synchronize do
|
|
48
|
+
if last_success_at.nil? || current_time > last_success_at
|
|
49
|
+
self.last_success_at = current_time
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
self.consecutive_errors = 0
|
|
53
|
+
self.consecutive_successes += 1
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Records failed circuit breaker execution
|
|
58
|
+
def record_failure(exception)
|
|
59
|
+
current_time = clock.current_time
|
|
60
|
+
failure = Domain::Failure.from_error(exception, time: current_time)
|
|
61
|
+
last_error_at = self.last_error_at
|
|
62
|
+
|
|
63
|
+
mutex.synchronize do
|
|
64
|
+
if last_error_at.nil? || failure.occurred_at > last_error_at
|
|
65
|
+
self.last_error = failure
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
self.consecutive_errors += 1
|
|
69
|
+
self.consecutive_successes = 0
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def clear
|
|
74
|
+
mutex.synchronize do
|
|
75
|
+
initialize_metrics
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
attr_accessor :consecutive_errors
|
|
82
|
+
attr_accessor :consecutive_successes
|
|
83
|
+
attr_accessor :last_error
|
|
84
|
+
attr_accessor :last_success_at
|
|
85
|
+
|
|
86
|
+
attr_reader :mutex
|
|
87
|
+
attr_reader :clock
|
|
88
|
+
|
|
89
|
+
def initialize_metrics
|
|
90
|
+
@consecutive_errors = 0
|
|
91
|
+
@consecutive_successes = 0
|
|
92
|
+
@last_error = nil
|
|
93
|
+
@last_success_at = nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def last_error_at
|
|
97
|
+
last_error&.occurred_at
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Memory
|
|
6
|
+
module Storage
|
|
7
|
+
# Thread-safe in-memory storage for time-windowed light @
|
|
8
|
+
#
|
|
9
|
+
# This class tracks success and failure counts within a sliding time window,
|
|
10
|
+
# along with consecutive counters and the most recent error. It's designed
|
|
11
|
+
# for single-process deployments where distributed coordination isn't needed.
|
|
12
|
+
#
|
|
13
|
+
# The sliding window approach provides more accurate error rate calculations
|
|
14
|
+
# than consecutive-error counting, as it considers the full picture of
|
|
15
|
+
# recent traffic rather than just the most recent streak.
|
|
16
|
+
#
|
|
17
|
+
# @note All public methods are synchronized via mutex to ensure thread safety.
|
|
18
|
+
#
|
|
19
|
+
class WindowMetrics
|
|
20
|
+
def initialize(window_size:, clock:)
|
|
21
|
+
@clock = clock
|
|
22
|
+
@mutex = Mutex.new
|
|
23
|
+
@window_size = window_size
|
|
24
|
+
|
|
25
|
+
initialize_metrics
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get metrics for the current light
|
|
29
|
+
def metrics_snapshot
|
|
30
|
+
mutex.synchronize do
|
|
31
|
+
window_start = (clock.current_time - @window_size)
|
|
32
|
+
errors = @errors.sum_in_window(window_start)
|
|
33
|
+
successes = @successes.sum_in_window(window_start)
|
|
34
|
+
|
|
35
|
+
Domain::MetricsSnapshot.new(
|
|
36
|
+
errors:,
|
|
37
|
+
successes:,
|
|
38
|
+
consecutive_errors: [@consecutive_errors, errors].min,
|
|
39
|
+
consecutive_successes: [@consecutive_successes, successes].min,
|
|
40
|
+
last_error: @last_error,
|
|
41
|
+
last_success_at: @last_success_at
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Records successful circuit breaker execution
|
|
47
|
+
def record_success
|
|
48
|
+
mutex.synchronize do
|
|
49
|
+
current_time = clock.current_time
|
|
50
|
+
@successes.increment
|
|
51
|
+
|
|
52
|
+
if @last_success_at.nil? || current_time > T.must(@last_success_at)
|
|
53
|
+
@last_success_at = current_time
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@consecutive_errors = 0
|
|
57
|
+
@consecutive_successes += 1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Records failed circuit breaker execution
|
|
62
|
+
def record_failure(exception)
|
|
63
|
+
mutex.synchronize do
|
|
64
|
+
@errors.increment
|
|
65
|
+
|
|
66
|
+
failure = Domain::Failure.from_error(exception, time: clock.current_time)
|
|
67
|
+
last_error_at = @last_error&.occurred_at
|
|
68
|
+
|
|
69
|
+
if last_error_at.nil? || failure.occurred_at > last_error_at
|
|
70
|
+
@last_error = failure
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
@consecutive_errors += 1
|
|
74
|
+
@consecutive_successes = 0
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def clear
|
|
79
|
+
mutex.synchronize do
|
|
80
|
+
initialize_metrics
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
attr_reader :mutex
|
|
87
|
+
attr_reader :clock
|
|
88
|
+
|
|
89
|
+
def initialize_metrics
|
|
90
|
+
@consecutive_errors = 0
|
|
91
|
+
@consecutive_successes = 0
|
|
92
|
+
@last_error = nil
|
|
93
|
+
@last_success_at = nil
|
|
94
|
+
@successes = Infrastructure::Memory::DataStore::SlidingWindow.new(clock:)
|
|
95
|
+
@errors = Infrastructure::Memory::DataStore::SlidingWindow.new(clock:)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -8,40 +8,29 @@ module Stoplight
|
|
|
8
8
|
# handle failures.
|
|
9
9
|
#
|
|
10
10
|
# @api private
|
|
11
|
-
class FailSafe
|
|
12
|
-
#
|
|
13
|
-
# @return [Stoplight::Domain::StateTransitionNotifier] The underlying notifier being wrapped.
|
|
11
|
+
class FailSafe
|
|
12
|
+
# The underlying notifier being wrapped.
|
|
14
13
|
attr_reader :notifier
|
|
15
|
-
|
|
16
|
-
# @!attribute [r] error_notifier
|
|
17
|
-
# @return [Stoplight::Domain::StateTransitionNotifier] The underlying notifier being wrapped.
|
|
14
|
+
# The underlying notifier being wrapped.
|
|
18
15
|
attr_reader :error_notifier
|
|
19
16
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# @param notifier [Stoplight::Domain::StateTransitionNotifier] The notifier to wrap.
|
|
23
|
-
# @param error_notifier [Proc] called when wrapped data store fails
|
|
17
|
+
# @param notifier The notifier to wrap.
|
|
18
|
+
# @param error_notifier called when wrapped data store fails
|
|
24
19
|
def initialize(notifier:, error_notifier:)
|
|
25
20
|
@notifier = notifier
|
|
26
21
|
@error_notifier = error_notifier
|
|
27
22
|
end
|
|
28
23
|
|
|
29
24
|
# Sends a notification using the wrapped notifier with fail-safe mechanisms.
|
|
30
|
-
|
|
31
|
-
# @param config [Stoplight::Domain::Config] The light configuration.
|
|
32
|
-
# @param from_color [String] The initial color of the light.
|
|
33
|
-
# @param to_color [String] The target color of the light.
|
|
34
|
-
# @param error [Exception, nil] An optional error to include in the notification.
|
|
35
|
-
# @return [void]
|
|
36
|
-
def notify(config, from_color, to_color, error = nil)
|
|
25
|
+
def notify(info, from_color, to_color, error = nil)
|
|
37
26
|
fallback = proc do |exception|
|
|
38
27
|
error_notifier.call(exception) if exception
|
|
39
28
|
nil
|
|
40
|
-
end
|
|
29
|
+
end #: ^(StandardError?) -> void
|
|
41
30
|
|
|
42
31
|
circuit_breaker.run(fallback) do
|
|
43
|
-
notifier.notify(
|
|
44
|
-
end
|
|
32
|
+
notifier.notify(info, from_color, to_color, error)
|
|
33
|
+
end #: void
|
|
45
34
|
end
|
|
46
35
|
|
|
47
36
|
# @return [Boolean]
|
|
@@ -49,7 +38,6 @@ module Stoplight
|
|
|
49
38
|
other.is_a?(self.class) && notifier == other.notifier
|
|
50
39
|
end
|
|
51
40
|
|
|
52
|
-
# @return [Stoplight::Light] The circuit breaker used to handle failures.
|
|
53
41
|
private def circuit_breaker
|
|
54
42
|
@circuit_breaker ||= Stoplight.system_light(
|
|
55
43
|
"stoplight:notifier:fail_safe:#{notifier.class.name}",
|
|
@@ -39,10 +39,8 @@ module Stoplight
|
|
|
39
39
|
# light.run { raise 'Simulated failure' } rescue nil
|
|
40
40
|
# light.run { raise 'Simulated failure' } rescue nil
|
|
41
41
|
#
|
|
42
|
-
module Generic
|
|
43
|
-
#
|
|
44
|
-
# @return [Proc] The formatter used to generate notification messages.
|
|
45
|
-
# @see Stoplight::Default::FORMATTER
|
|
42
|
+
module Generic
|
|
43
|
+
# The formatter used to generate notification messages.
|
|
46
44
|
attr_reader :formatter
|
|
47
45
|
|
|
48
46
|
DEFAULT_FORMATTER = lambda do |light, from_color, to_color, error|
|
|
@@ -52,8 +50,8 @@ module Stoplight
|
|
|
52
50
|
end
|
|
53
51
|
public_constant :DEFAULT_FORMATTER
|
|
54
52
|
|
|
55
|
-
# @param object
|
|
56
|
-
# @param formatter
|
|
53
|
+
# @param object The object used by the notifier (e.g., a logger or external service).
|
|
54
|
+
# @param formatter A custom formatter for generating notification messages.
|
|
57
55
|
# If no formatter is provided, the default formatter is used.
|
|
58
56
|
def initialize(object, formatter = nil)
|
|
59
57
|
@object = object
|
|
@@ -61,12 +59,6 @@ module Stoplight
|
|
|
61
59
|
end
|
|
62
60
|
|
|
63
61
|
# Sends a notification when a Stoplight changes state.
|
|
64
|
-
#
|
|
65
|
-
# @param light [Light] The Stoplight instance triggering the notification.
|
|
66
|
-
# @param from_color [String] The previous state color of the Stoplight.
|
|
67
|
-
# @param to_color [String] The new state color of the Stoplight.
|
|
68
|
-
# @param error [Exception, nil] The error (if any) that caused the state change.
|
|
69
|
-
# @return [String] The formatted notification message.
|
|
70
62
|
def notify(light, from_color, to_color, error)
|
|
71
63
|
message = formatter.call(light, from_color, to_color, error)
|
|
72
64
|
put(message)
|
|
@@ -77,8 +69,6 @@ module Stoplight
|
|
|
77
69
|
|
|
78
70
|
# Processes the notification message.
|
|
79
71
|
#
|
|
80
|
-
# @param message [String] The notification message to be processed.
|
|
81
|
-
# @raise [NotImplementedError] If the method is not implemented in a subclass.
|
|
82
72
|
# :nocov:
|
|
83
73
|
def put(message)
|
|
84
74
|
raise NotImplementedError
|
data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_store.rb
RENAMED
|
@@ -5,9 +5,9 @@ require "forwardable"
|
|
|
5
5
|
|
|
6
6
|
module Stoplight
|
|
7
7
|
module Infrastructure
|
|
8
|
-
module
|
|
9
|
-
class
|
|
10
|
-
# Distributed recovery
|
|
8
|
+
module Redis
|
|
9
|
+
class DataStore
|
|
10
|
+
# Distributed recovery lock using Redis SET NX (set-if-not-exists).
|
|
11
11
|
#
|
|
12
12
|
# Lock Acquisition:
|
|
13
13
|
# - Uses unique UUID token to prevent accidental release of others' locks
|
|
@@ -25,29 +25,12 @@ module Stoplight
|
|
|
25
25
|
# - Release failure: Lock auto-expires after lock_timeout
|
|
26
26
|
#
|
|
27
27
|
class RecoveryLockStore
|
|
28
|
-
# @!attribute redis
|
|
29
|
-
# @return [RedisClient]
|
|
30
|
-
protected attr_reader :redis
|
|
31
|
-
|
|
32
|
-
# @!attribute lock_timeout
|
|
33
|
-
# @return [Integer]
|
|
34
|
-
protected attr_reader :lock_timeout
|
|
35
|
-
|
|
36
|
-
# @!attribute scripting
|
|
37
|
-
# @return [Stoplight::Infrastructure::DataStore::Redis::Scripting]
|
|
38
|
-
protected attr_reader :scripting
|
|
39
|
-
|
|
40
|
-
# @param redis [RedisClient | ConnectionPool]
|
|
41
|
-
# @param lock_timeout [Integer] recovery_lock timeout in milliseconds
|
|
42
|
-
# @param scripting [Stoplight::Infrastructure::DataStore::Redis::Scripting]
|
|
43
28
|
def initialize(redis:, lock_timeout:, scripting:)
|
|
44
29
|
@redis = redis
|
|
45
30
|
@lock_timeout = lock_timeout
|
|
46
31
|
@scripting = scripting
|
|
47
32
|
end
|
|
48
33
|
|
|
49
|
-
# @param light_name [String]
|
|
50
|
-
# @return [Stoplight::Infrastructure::DataStore::Redis::RecoveryLockToken, nil]
|
|
51
34
|
def acquire_lock(light_name)
|
|
52
35
|
recovery_lock = RecoveryLockToken.new(light_name:)
|
|
53
36
|
|
|
@@ -58,14 +41,18 @@ module Stoplight
|
|
|
58
41
|
recovery_lock if acquired
|
|
59
42
|
end
|
|
60
43
|
|
|
61
|
-
# @param recovery_lock [Stoplight::Infrastructure::DataStore::Redis::RecoveryLockToken]
|
|
62
|
-
# @return [void]
|
|
63
44
|
def release_lock(recovery_lock)
|
|
64
45
|
scripting.call(
|
|
65
46
|
:release_lock,
|
|
66
47
|
keys: [recovery_lock.lock_key], args: [recovery_lock.token]
|
|
67
48
|
)
|
|
68
49
|
end
|
|
50
|
+
|
|
51
|
+
protected
|
|
52
|
+
|
|
53
|
+
attr_reader :redis
|
|
54
|
+
attr_reader :lock_timeout
|
|
55
|
+
attr_reader :scripting
|
|
69
56
|
end
|
|
70
57
|
end
|
|
71
58
|
end
|
data/lib/stoplight/infrastructure/{data_store/redis → redis/data_store}/recovery_lock_token.rb
RENAMED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
|
-
require "forwardable"
|
|
5
4
|
|
|
6
5
|
module Stoplight
|
|
7
6
|
module Infrastructure
|
|
8
|
-
module
|
|
9
|
-
class
|
|
10
|
-
class RecoveryLockToken
|
|
11
|
-
extend Forwardable
|
|
12
|
-
|
|
13
|
-
def_delegator "Stoplight::Infrastructure::DataStore::Redis", :key
|
|
14
|
-
private :key
|
|
15
|
-
|
|
16
|
-
# @!attribute light_name
|
|
17
|
-
# @return [String]
|
|
7
|
+
module Redis
|
|
8
|
+
class DataStore
|
|
9
|
+
class RecoveryLockToken
|
|
18
10
|
attr_reader :light_name
|
|
19
|
-
|
|
20
|
-
# @!attribute token
|
|
21
|
-
# @return [String]
|
|
22
11
|
attr_reader :token
|
|
23
12
|
|
|
24
13
|
# @param light_name [String]
|
|
@@ -28,6 +17,10 @@ module Stoplight
|
|
|
28
17
|
end
|
|
29
18
|
|
|
30
19
|
def lock_key = key(:locks, :recovery, light_name)
|
|
20
|
+
|
|
21
|
+
private def key(*parts)
|
|
22
|
+
Stoplight::Infrastructure::Redis::DataStore.key(*parts)
|
|
23
|
+
end
|
|
31
24
|
end
|
|
32
25
|
end
|
|
33
26
|
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Stoplight
|
|
4
4
|
module Infrastructure
|
|
5
|
-
module
|
|
6
|
-
class
|
|
5
|
+
module Redis
|
|
6
|
+
class DataStore
|
|
7
7
|
# Manages Lua scripts for Redis operations.
|
|
8
8
|
#
|
|
9
9
|
# This class provides execution of Lua scripts by caching their SHA digests
|
|
@@ -16,22 +16,15 @@ module Stoplight
|
|
|
16
16
|
# @note Scripts are loaded lazily on first use and cached in memory
|
|
17
17
|
# @note Script files must be named `<script_name>.lua` and located in scripts_root
|
|
18
18
|
class Scripting
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
protected attr_reader :scripts_root
|
|
19
|
+
current_dir = __dir__ or raise "Cannot determine script directory"
|
|
20
|
+
SCRIPTS_ROOT = File.join(current_dir, "lua_scripts")
|
|
21
|
+
private_constant :SCRIPTS_ROOT
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# @!attribute redis
|
|
29
|
-
# @return [RedisClient | ConnectionPool]
|
|
30
|
-
protected attr_reader :redis
|
|
23
|
+
class << self
|
|
24
|
+
def default_scripts_root = SCRIPTS_ROOT
|
|
25
|
+
end
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
# @param scripts_root [String]
|
|
34
|
-
def initialize(redis:, scripts_root: SCRIPTS_ROOT)
|
|
27
|
+
def initialize(redis:, scripts_root: self.class.default_scripts_root)
|
|
35
28
|
@scripts_root = scripts_root
|
|
36
29
|
@redis = redis
|
|
37
30
|
@shas = {}
|
|
@@ -39,7 +32,7 @@ module Stoplight
|
|
|
39
32
|
|
|
40
33
|
def call(script_name, keys: [], args: [])
|
|
41
34
|
redis.then do |client|
|
|
42
|
-
client.evalsha(script_sha(script_name), keys: keys, argv: args)
|
|
35
|
+
client.evalsha(script_sha(script_name), keys: keys.map(&:to_s), argv: args.map(&:to_s))
|
|
43
36
|
end
|
|
44
37
|
rescue ::Redis::CommandError => error
|
|
45
38
|
if error.message.include?("NOSCRIPT")
|
|
@@ -50,18 +43,27 @@ module Stoplight
|
|
|
50
43
|
end
|
|
51
44
|
end
|
|
52
45
|
|
|
53
|
-
|
|
46
|
+
protected
|
|
47
|
+
|
|
48
|
+
attr_reader :scripts_root
|
|
49
|
+
attr_reader :redis
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
attr_reader :shas
|
|
54
|
+
|
|
55
|
+
def reload_script(script_name)
|
|
54
56
|
shas.delete(script_name)
|
|
55
57
|
script_sha(script_name)
|
|
56
58
|
end
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
def script_sha(script_name)
|
|
59
61
|
if shas.key?(script_name)
|
|
60
62
|
shas[script_name]
|
|
61
63
|
else
|
|
62
64
|
script = File.read(File.join(scripts_root, "#{script_name}.lua"))
|
|
63
65
|
|
|
64
|
-
shas[script_name] = redis.then { |client| client.script(
|
|
66
|
+
shas[script_name] = redis.then { |client| client.script(:load, script) }
|
|
65
67
|
end
|
|
66
68
|
end
|
|
67
69
|
end
|