stoplight 5.7.0 → 5.8.2
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} +47 -55
- 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,28 @@
|
|
|
1
|
+
local failure_ts = tonumber(ARGV[1])
|
|
2
|
+
local failure_json = ARGV[2]
|
|
3
|
+
local metrics_ttl = tonumber(ARGV[3])
|
|
4
|
+
|
|
5
|
+
local metrics_key = KEYS[1]
|
|
6
|
+
|
|
7
|
+
-- Update metadata
|
|
8
|
+
local meta = redis.call('HMGET', metrics_key, 'last_error_at', 'consecutive_errors')
|
|
9
|
+
local prev_failure_ts = tonumber(meta[1])
|
|
10
|
+
local prev_consecutive_errors = tonumber(meta[2])
|
|
11
|
+
|
|
12
|
+
if not prev_failure_ts or failure_ts > prev_failure_ts then
|
|
13
|
+
redis.call(
|
|
14
|
+
'HSET', metrics_key,
|
|
15
|
+
'last_error_at', failure_ts,
|
|
16
|
+
'last_error_json', failure_json,
|
|
17
|
+
'consecutive_errors', (prev_consecutive_errors or 0) + 1,
|
|
18
|
+
'consecutive_successes', 0
|
|
19
|
+
)
|
|
20
|
+
else
|
|
21
|
+
redis.call(
|
|
22
|
+
'HSET', metrics_key,
|
|
23
|
+
'consecutive_errors', (prev_consecutive_errors or 0) + 1,
|
|
24
|
+
'consecutive_successes', 0
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
redis.call('EXPIRE', metrics_key, metrics_ttl)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
local request_ts = tonumber(ARGV[1])
|
|
2
|
+
local metrics_ttl = tonumber(ARGV[2])
|
|
3
|
+
|
|
4
|
+
local metrics_key = KEYS[1]
|
|
5
|
+
|
|
6
|
+
-- Update metadata
|
|
7
|
+
local meta = redis.call('HMGET', metrics_key, 'last_success_at', 'consecutive_successes')
|
|
8
|
+
local prev_success_ts = tonumber(meta[1])
|
|
9
|
+
local prev_consecutive_successes = tonumber(meta[2])
|
|
10
|
+
|
|
11
|
+
if not prev_success_ts or request_ts > prev_success_ts then
|
|
12
|
+
redis.call(
|
|
13
|
+
'HSET', metrics_key,
|
|
14
|
+
'last_success_at', request_ts,
|
|
15
|
+
'consecutive_errors', 0,
|
|
16
|
+
'consecutive_successes', (prev_consecutive_successes or 0) + 1
|
|
17
|
+
)
|
|
18
|
+
else
|
|
19
|
+
redis.call(
|
|
20
|
+
'HSET', metrics_key,
|
|
21
|
+
'consecutive_errors', 0,
|
|
22
|
+
'consecutive_successes', (prev_consecutive_successes or 0) + 1
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
redis.call('EXPIRE', metrics_key, metrics_ttl)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Redis
|
|
6
|
+
module Storage
|
|
7
|
+
# Distributed metrics storage for consecutive-error light strategies.
|
|
8
|
+
#
|
|
9
|
+
# This class provides a lightweight alternative to +WindowMetrics+ for circuit
|
|
10
|
+
# breakers that don't need time-windowed rate calculations. It tracks only:
|
|
11
|
+
# - Consecutive success/failure counters (reset on opposite outcome)
|
|
12
|
+
# - Most recent error with timestamp and serialized details
|
|
13
|
+
# - Most recent success timestamp
|
|
14
|
+
#
|
|
15
|
+
# == Storage Structure
|
|
16
|
+
#
|
|
17
|
+
# All data is stored in a single Redis hash:
|
|
18
|
+
# stoplight:{version}:{system}:{light}:metrics
|
|
19
|
+
#
|
|
20
|
+
# Hash fields:
|
|
21
|
+
# - +last_success_at+: Unix timestamp (float) of most recent success
|
|
22
|
+
# - +last_error_at+: Unix timestamp (float) of most recent failure
|
|
23
|
+
# - +last_error_json+: Serialized {Domain::Failure} for error details
|
|
24
|
+
# - +consecutive_successes+: Integer counter, reset to 0 on failure
|
|
25
|
+
# - +consecutive_errors+: Integer counter, reset to 0 on success
|
|
26
|
+
#
|
|
27
|
+
# == Performance Characteristics
|
|
28
|
+
#
|
|
29
|
+
# This implementation is optimized for minimal Redis overhead:
|
|
30
|
+
# - Single hash key per circuit (vs. multiple ZSETs for +WindowMetrics+)
|
|
31
|
+
# - O(1) reads and writes
|
|
32
|
+
# - No time-range queries or bucket management
|
|
33
|
+
# - Low memory footprint
|
|
34
|
+
#
|
|
35
|
+
# == Atomicity
|
|
36
|
+
#
|
|
37
|
+
# Record operations use Lua scripts to ensure atomic read-modify-write:
|
|
38
|
+
# - Consecutive counters are incremented and reset in one round-trip
|
|
39
|
+
# - "Last" timestamps only update if the new event is more recent,
|
|
40
|
+
# preventing out-of-order writes from corrupting state
|
|
41
|
+
#
|
|
42
|
+
# == When to Use
|
|
43
|
+
#
|
|
44
|
+
# Choose UnboundedMetrics when your circuit breaker strategy is based on
|
|
45
|
+
# consecutive failures (e.g., "open after 5 failures in a row"). Choose
|
|
46
|
+
# {WindowMetrics} when you need error rate calculations (e.g., "open when
|
|
47
|
+
# error rate exceeds 50% over 5 minutes").
|
|
48
|
+
#
|
|
49
|
+
# @note The +errors+ and +successes+ fields in the returned +Stoplight::Domain::Metrics+
|
|
50
|
+
# are always +nil+ since this class doesn't track windowed totals.
|
|
51
|
+
#
|
|
52
|
+
# @note The metrics hash TTL is refreshed on every write operation. Circuits
|
|
53
|
+
# with no activity will have their metrics expire, which is generally
|
|
54
|
+
# desirable for ephemeral or decommissioned lights.
|
|
55
|
+
#
|
|
56
|
+
class UnboundedMetrics < Metrics
|
|
57
|
+
def initialize(redis:, scripting:, key_space:, clock:)
|
|
58
|
+
@clock = clock
|
|
59
|
+
@scripting = scripting
|
|
60
|
+
@redis = redis
|
|
61
|
+
@metrics_key = key_space.key(:metrics)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Get metrics for the current light
|
|
65
|
+
#
|
|
66
|
+
def metrics_snapshot
|
|
67
|
+
last_success_at, last_error_json, consecutive_errors, consecutive_successes = redis.with do |client|
|
|
68
|
+
client.hmget(
|
|
69
|
+
metrics_key,
|
|
70
|
+
"last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes"
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
Domain::MetricsSnapshot.new(
|
|
75
|
+
successes: nil, errors: nil,
|
|
76
|
+
consecutive_errors: consecutive_errors.to_i,
|
|
77
|
+
consecutive_successes: consecutive_successes.to_i,
|
|
78
|
+
last_error: deserialize_failure(last_error_json),
|
|
79
|
+
last_success_at: (clock.at(last_success_at.to_f) if last_success_at)
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Records successful circuit breaker execution
|
|
84
|
+
#
|
|
85
|
+
def record_success
|
|
86
|
+
timestamp = clock.current_time.to_f
|
|
87
|
+
|
|
88
|
+
scripting.call(
|
|
89
|
+
:"unbounded_metrics/record_success",
|
|
90
|
+
args: [timestamp, metrics_ttl],
|
|
91
|
+
keys: [metrics_key]
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Records failed circuit breaker execution
|
|
96
|
+
#
|
|
97
|
+
def record_failure(exception)
|
|
98
|
+
timestamp = clock.current_time.to_f
|
|
99
|
+
|
|
100
|
+
scripting.call(
|
|
101
|
+
:"unbounded_metrics/record_failure",
|
|
102
|
+
args: [timestamp, serialize_exception(exception, timestamp:), metrics_ttl],
|
|
103
|
+
keys: [metrics_key]
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def clear
|
|
108
|
+
redis.with do |client|
|
|
109
|
+
client.hdel(metrics_key, "last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes")
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
attr_reader :redis
|
|
116
|
+
attr_reader :scripting
|
|
117
|
+
attr_reader :metrics_key
|
|
118
|
+
attr_reader :clock
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
local number_of_metric_buckets = tonumber(ARGV[1])
|
|
2
|
+
local window_start_ts = tonumber(ARGV[2])
|
|
3
|
+
local window_end_ts = tonumber(ARGV[3])
|
|
4
|
+
local metrics_fields = {}
|
|
5
|
+
for idx = 4, #ARGV do
|
|
6
|
+
table.insert(metrics_fields, ARGV[idx])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
local metrics_key = KEYS[1]
|
|
10
|
+
|
|
11
|
+
local function count_events(start_idx, bucket_count, start_ts)
|
|
12
|
+
local total = 0
|
|
13
|
+
for idx = start_idx, start_idx + bucket_count - 1 do
|
|
14
|
+
total = total + tonumber(redis.call('ZCOUNT', KEYS[idx], start_ts, window_end_ts))
|
|
15
|
+
end
|
|
16
|
+
return total
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
local offset = 2
|
|
20
|
+
local successes = count_events(2, number_of_metric_buckets, window_start_ts)
|
|
21
|
+
|
|
22
|
+
offset = offset + number_of_metric_buckets
|
|
23
|
+
local errors = count_events(offset, number_of_metric_buckets, window_start_ts)
|
|
24
|
+
|
|
25
|
+
local metrics = redis.call('HMGET', metrics_key, unpack(metrics_fields))
|
|
26
|
+
return {successes, errors, unpack(metrics)}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
local failure_ts = tonumber(ARGV[1])
|
|
2
|
+
local failure_id = ARGV[2]
|
|
3
|
+
local failure_json = ARGV[3]
|
|
4
|
+
local bucket_ttl = tonumber(ARGV[4])
|
|
5
|
+
local metadata_ttl = tonumber(ARGV[5])
|
|
6
|
+
|
|
7
|
+
local metrics_key = KEYS[1]
|
|
8
|
+
local failures_key = KEYS[2]
|
|
9
|
+
|
|
10
|
+
-- Record failure
|
|
11
|
+
if failures_key ~= nil then
|
|
12
|
+
redis.call('ZADD', failures_key, failure_ts, failure_id)
|
|
13
|
+
redis.call('EXPIRE', failures_key, bucket_ttl) -- Not supported in Redis 6.2:, 'NX')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
-- Update metadata
|
|
17
|
+
local meta = redis.call('HMGET', metrics_key, 'last_error_at', 'consecutive_errors')
|
|
18
|
+
local prev_failure_ts = tonumber(meta[1])
|
|
19
|
+
local prev_consecutive_errors = tonumber(meta[2])
|
|
20
|
+
|
|
21
|
+
if not prev_failure_ts or failure_ts > prev_failure_ts then
|
|
22
|
+
redis.call(
|
|
23
|
+
'HSET', metrics_key,
|
|
24
|
+
'last_error_at', failure_ts,
|
|
25
|
+
'last_error_json', failure_json,
|
|
26
|
+
'consecutive_errors', (prev_consecutive_errors or 0) + 1,
|
|
27
|
+
'consecutive_successes', 0
|
|
28
|
+
)
|
|
29
|
+
else
|
|
30
|
+
redis.call(
|
|
31
|
+
'HSET', metrics_key,
|
|
32
|
+
'consecutive_errors', (prev_consecutive_errors or 0) + 1,
|
|
33
|
+
'consecutive_successes', 0
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
redis.call('EXPIRE', metrics_key, metadata_ttl) -- Not supported in Redis 6.2:, 'GT')
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
local request_ts = tonumber(ARGV[1])
|
|
2
|
+
local request_id = ARGV[2]
|
|
3
|
+
local bucket_ttl = tonumber(ARGV[3])
|
|
4
|
+
local metadata_ttl = tonumber(ARGV[4])
|
|
5
|
+
|
|
6
|
+
local metrics_key = KEYS[1]
|
|
7
|
+
local successes_key = KEYS[2]
|
|
8
|
+
|
|
9
|
+
-- Record success
|
|
10
|
+
if successes_key ~= nil then
|
|
11
|
+
redis.call('ZADD', successes_key, request_ts, request_id)
|
|
12
|
+
redis.call('EXPIRE', successes_key, bucket_ttl) -- Not supported in Redis 6.2:, 'NX')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
-- Update metadata
|
|
16
|
+
local meta = redis.call('HMGET', metrics_key, 'last_success_at', 'consecutive_successes')
|
|
17
|
+
local prev_success_ts = tonumber(meta[1])
|
|
18
|
+
local prev_consecutive_successes = tonumber(meta[2])
|
|
19
|
+
|
|
20
|
+
if not prev_success_ts or request_ts > prev_success_ts then
|
|
21
|
+
redis.call(
|
|
22
|
+
'HSET', metrics_key,
|
|
23
|
+
'last_success_at', request_ts,
|
|
24
|
+
'consecutive_errors', 0,
|
|
25
|
+
'consecutive_successes', (prev_consecutive_successes or 0) + 1
|
|
26
|
+
)
|
|
27
|
+
else
|
|
28
|
+
redis.call(
|
|
29
|
+
'HSET', metrics_key,
|
|
30
|
+
'consecutive_errors', 0,
|
|
31
|
+
'consecutive_successes', (prev_consecutive_successes or 0) + 1
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
redis.call('EXPIRE', metrics_key, metadata_ttl) -- Not supported in Redis 6.2:, 'GT')
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Redis
|
|
6
|
+
module Storage
|
|
7
|
+
# Distributed storage for time-windowed light metrics using Redis.
|
|
8
|
+
#
|
|
9
|
+
# This class implements sliding window metrics using Redis sorted sets (ZSETs)
|
|
10
|
+
# for efficient time-range queries. Events are bucketed by hour to bound memory
|
|
11
|
+
# usage and enable automatic expiration via Redis TTLs.
|
|
12
|
+
#
|
|
13
|
+
# == Storage Structure
|
|
14
|
+
#
|
|
15
|
+
# Events are stored in hourly buckets as ZSETs:
|
|
16
|
+
# stoplight:{version}:{system}:{light}:window_metrics:success:1696154400
|
|
17
|
+
# stoplight:{version}:{system}:{light}:window_metrics:failure:1696154400
|
|
18
|
+
#
|
|
19
|
+
# Each ZSET member is a unique event ID with its timestamp as the score,
|
|
20
|
+
# enabling O(log N) range queries via ZCOUNT.
|
|
21
|
+
#
|
|
22
|
+
# Metadata (consecutive counters, last error) is stored in a hash:
|
|
23
|
+
# stoplight:{version}:{system}:{light}:window_metrics
|
|
24
|
+
#
|
|
25
|
+
# == Bucket Strategy
|
|
26
|
+
#
|
|
27
|
+
# Fixed 1-hour buckets provide a balance between:
|
|
28
|
+
# - Query efficiency: At most ~25 buckets for a 24-hour window
|
|
29
|
+
# - Memory efficiency: Natural expiration without manual cleanup
|
|
30
|
+
# - Precision: Sub-bucket accuracy via ZSET scores
|
|
31
|
+
#
|
|
32
|
+
# == Atomicity
|
|
33
|
+
#
|
|
34
|
+
# All operations use Lua scripts to ensure atomicity:
|
|
35
|
+
# - record_success: Increments counter and updates metadata in one round-trip
|
|
36
|
+
# - record_failure: Same, plus stores serialized error details
|
|
37
|
+
# - metrics_snapshot: Aggregates across buckets atomically
|
|
38
|
+
#
|
|
39
|
+
class WindowMetrics < Metrics
|
|
40
|
+
def initialize(redis:, scripting:, config:, clock:, key_space:)
|
|
41
|
+
@clock = clock
|
|
42
|
+
@scripting = scripting
|
|
43
|
+
@redis = redis
|
|
44
|
+
@config = config
|
|
45
|
+
@key_space = key_space
|
|
46
|
+
@metrics_key = key_space.key(:window_metrics)
|
|
47
|
+
@window_size = T.must(config.window_size).to_i
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get metrics for the current light
|
|
51
|
+
#
|
|
52
|
+
# @return [Stoplight::Domain::Metrics]
|
|
53
|
+
def metrics_snapshot
|
|
54
|
+
window_end_ts = clock.current_time.to_f
|
|
55
|
+
window_start_ts = window_end_ts - @window_size
|
|
56
|
+
failure_keys = failure_bucket_keys(window_end_ts)
|
|
57
|
+
success_keys = success_bucket_keys(window_end_ts)
|
|
58
|
+
|
|
59
|
+
successes, errors, last_success_at, last_error_json, consecutive_errors, consecutive_successes = scripting.call(
|
|
60
|
+
:"window_metrics/metrics_snapshot",
|
|
61
|
+
args: [
|
|
62
|
+
failure_keys.count,
|
|
63
|
+
window_start_ts,
|
|
64
|
+
window_end_ts,
|
|
65
|
+
"last_success_at", "last_error_json", "consecutive_errors", "consecutive_successes"
|
|
66
|
+
],
|
|
67
|
+
keys: [
|
|
68
|
+
metrics_key,
|
|
69
|
+
*success_keys,
|
|
70
|
+
*failure_keys
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
Domain::MetricsSnapshot.new(
|
|
74
|
+
successes:, errors:,
|
|
75
|
+
consecutive_errors: [consecutive_errors.to_i, errors].min,
|
|
76
|
+
consecutive_successes: [consecutive_successes.to_i, successes].min,
|
|
77
|
+
last_error: deserialize_failure(last_error_json),
|
|
78
|
+
last_success_at: (clock.at(last_success_at.to_f) if last_success_at)
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Records successful circuit breaker execution
|
|
83
|
+
#
|
|
84
|
+
# @return [void]
|
|
85
|
+
def record_success
|
|
86
|
+
timestamp = clock.current_time.to_f
|
|
87
|
+
|
|
88
|
+
scripting.call(
|
|
89
|
+
:"window_metrics/record_success",
|
|
90
|
+
args: [timestamp, SecureRandom.hex(12), bucket_ttl, metrics_ttl],
|
|
91
|
+
keys: [
|
|
92
|
+
metrics_key,
|
|
93
|
+
successes_key(time: timestamp)
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Records failed circuit breaker execution
|
|
99
|
+
#
|
|
100
|
+
# @param exception [StandardError]
|
|
101
|
+
# @return [void]
|
|
102
|
+
def record_failure(exception)
|
|
103
|
+
timestamp = clock.current_time.to_f
|
|
104
|
+
|
|
105
|
+
scripting.call(
|
|
106
|
+
:"window_metrics/record_failure",
|
|
107
|
+
args: [timestamp, SecureRandom.hex(12), serialize_exception(exception, timestamp:), bucket_ttl, metrics_ttl],
|
|
108
|
+
keys: [metrics_key, errors_key(time: timestamp)]
|
|
109
|
+
)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def clear
|
|
113
|
+
window_end_ts = clock.current_time.to_f
|
|
114
|
+
failure_keys = failure_bucket_keys(window_end_ts)
|
|
115
|
+
success_keys = success_bucket_keys(window_end_ts)
|
|
116
|
+
|
|
117
|
+
redis.with do |client|
|
|
118
|
+
client.del(metrics_key, *failure_keys, *success_keys)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Generates a Redis key for a specific metric and time.
|
|
123
|
+
#
|
|
124
|
+
# @param metric [Symbol] The metric type (e.g., "errors").
|
|
125
|
+
# @param time [Time, Numeric] The time for which to generate the key.
|
|
126
|
+
# @return [String] The generated Redis key.
|
|
127
|
+
def bucket_key(metric:, time:)
|
|
128
|
+
key_space.key(:window_metrics, metric, (time.to_i / bucket_size) * bucket_size)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Retrieves the list of Redis bucket keys required to cover a specific time window.
|
|
132
|
+
#
|
|
133
|
+
# @param metric The metric type (e.g., "errors").
|
|
134
|
+
# @param window_end The end time of the window (can be a Time object or a numeric timestamp).
|
|
135
|
+
# @return A list of Redis keys for the buckets that cover the time window.
|
|
136
|
+
# @api private
|
|
137
|
+
def buckets_for_window(metric:, window_end:)
|
|
138
|
+
window_end_ts = window_end.to_i
|
|
139
|
+
window_start_ts = window_end_ts - @window_size
|
|
140
|
+
|
|
141
|
+
# Find bucket timestamps that contain any part of the window
|
|
142
|
+
start_bucket = (window_start_ts / bucket_size) * bucket_size
|
|
143
|
+
|
|
144
|
+
# End bucket is the last bucket that contains data within our window
|
|
145
|
+
end_bucket = ((window_end_ts - 1) / bucket_size) * bucket_size
|
|
146
|
+
|
|
147
|
+
(start_bucket..end_bucket).step(bucket_size).map do |bucket_start|
|
|
148
|
+
bucket_key(metric: metric, time: bucket_start)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
private
|
|
153
|
+
|
|
154
|
+
attr_reader :redis
|
|
155
|
+
attr_reader :scripting
|
|
156
|
+
attr_reader :metrics_key
|
|
157
|
+
attr_reader :clock
|
|
158
|
+
attr_reader :key_space
|
|
159
|
+
|
|
160
|
+
def bucket_size = 3600 # 1 hour
|
|
161
|
+
def bucket_ttl = @window_size + bucket_size
|
|
162
|
+
|
|
163
|
+
def successes_key(time:) = bucket_key(metric: :success, time:)
|
|
164
|
+
|
|
165
|
+
def errors_key(time:) = bucket_key(metric: :failure, time:)
|
|
166
|
+
|
|
167
|
+
def failure_bucket_keys(window_end) = buckets_for_window(metric: :failure, window_end:)
|
|
168
|
+
|
|
169
|
+
def success_bucket_keys(window_end) = buckets_for_window(metric: :success, window_end:)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -19,13 +19,10 @@ module Stoplight
|
|
|
19
19
|
# )
|
|
20
20
|
# metrics.record_success
|
|
21
21
|
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
private attr_reader :config
|
|
22
|
+
class CompatibilityMetrics
|
|
23
|
+
attr_reader :data_store
|
|
24
|
+
attr_reader :config
|
|
26
25
|
|
|
27
|
-
# @param data_store [Stoplight::Domain::DataStore]
|
|
28
|
-
# @param config [Stoplight::Domain::Config]
|
|
29
26
|
def initialize(data_store:, config:)
|
|
30
27
|
@data_store = data_store
|
|
31
28
|
@config = config
|
|
@@ -33,14 +30,10 @@ module Stoplight
|
|
|
33
30
|
|
|
34
31
|
def metrics_snapshot = data_store.get_metrics(config)
|
|
35
32
|
|
|
36
|
-
# @return [void]
|
|
37
33
|
def record_success = data_store.record_success(config)
|
|
38
34
|
|
|
39
|
-
# @param error [StandardError]
|
|
40
|
-
# @return [void]
|
|
41
35
|
def record_failure(error) = data_store.record_failure(config, error)
|
|
42
36
|
|
|
43
|
-
# @return [void]
|
|
44
37
|
def clear = data_store.clear_metrics(config)
|
|
45
38
|
end
|
|
46
39
|
end
|
|
@@ -12,24 +12,21 @@ module Stoplight
|
|
|
12
12
|
# This adapter will be removed in a future versions once all
|
|
13
13
|
# data stores have native recovery lock implementations.
|
|
14
14
|
#
|
|
15
|
-
# @see Stoplight::Domain::
|
|
16
|
-
class CompatibilityRecoveryLock
|
|
17
|
-
private attr_reader :data_store
|
|
18
|
-
private attr_reader :config
|
|
19
|
-
|
|
20
|
-
# @param data_store [Stoplight::Domain::DataStore]
|
|
21
|
-
# @param config [Stoplight::Domain::Config]
|
|
15
|
+
# @see Stoplight::Domain::_RecoveryLockStore
|
|
16
|
+
class CompatibilityRecoveryLock
|
|
22
17
|
def initialize(data_store:, config:)
|
|
23
18
|
@data_store = data_store
|
|
24
19
|
@config = config
|
|
25
20
|
end
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
def acquire_lock = data_store.acquire_recovery_lock(config)
|
|
22
|
+
def acquire_lock = data_store.acquire_recovery_lock(config) #: Domain::Storage::RecoveryLockToken?
|
|
29
23
|
|
|
30
|
-
# @param lock [Stoplight::Domain::LockToken]
|
|
31
|
-
# @return [void]
|
|
32
24
|
def release_lock(lock) = data_store.release_recovery_lock(lock)
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :data_store
|
|
29
|
+
attr_reader :config
|
|
33
30
|
end
|
|
34
31
|
end
|
|
35
32
|
end
|
|
@@ -21,13 +21,7 @@ module Stoplight
|
|
|
21
21
|
# recovery_metrics.record_success
|
|
22
22
|
# recovery_metrics.metrics_snapshot # => 1 success, 0 failures
|
|
23
23
|
#
|
|
24
|
-
|
|
25
|
-
class CompatibilityRecoveryMetrics < Domain::Storage::Metrics
|
|
26
|
-
private attr_reader :data_store
|
|
27
|
-
private attr_reader :config
|
|
28
|
-
|
|
29
|
-
# @param data_store [Stoplight::Domain::DataStore]
|
|
30
|
-
# @param config [Stoplight::Domain::Config]
|
|
24
|
+
class CompatibilityRecoveryMetrics
|
|
31
25
|
def initialize(data_store:, config:)
|
|
32
26
|
@data_store = data_store
|
|
33
27
|
@config = config
|
|
@@ -36,19 +30,17 @@ module Stoplight
|
|
|
36
30
|
def metrics_snapshot = data_store.get_recovery_metrics(config)
|
|
37
31
|
|
|
38
32
|
# Tracks successful circuit breaker execution
|
|
39
|
-
#
|
|
40
|
-
# @return [void]
|
|
41
33
|
def record_success = data_store.record_recovery_probe_success(config)
|
|
42
34
|
|
|
43
35
|
# Tracks failed circuit breaker execution
|
|
44
|
-
#
|
|
45
|
-
# @param error [StandardError]
|
|
46
|
-
# @return [void]
|
|
47
36
|
def record_failure(error) = data_store.record_recovery_probe_failure(config, error)
|
|
48
37
|
|
|
49
|
-
# Clears metrics
|
|
50
|
-
# @return [void]
|
|
51
38
|
def clear = data_store.clear_recovery_metrics(config)
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :data_store
|
|
43
|
+
attr_reader :config
|
|
52
44
|
end
|
|
53
45
|
end
|
|
54
46
|
end
|
|
@@ -20,35 +20,24 @@ module Stoplight
|
|
|
20
20
|
# state.set_state(State::LOCKED_RED)
|
|
21
21
|
# snapshot = state.state_snapshot
|
|
22
22
|
#
|
|
23
|
-
class CompatibilityState
|
|
24
|
-
# @!attribute data_store
|
|
25
|
-
# @return [Stoplight::Domain::DataStore]
|
|
26
|
-
private attr_reader :data_store
|
|
27
|
-
|
|
28
|
-
# @!attribute config
|
|
29
|
-
# @return [Stoplight::Domain::Config]
|
|
30
|
-
private attr_reader :config
|
|
31
|
-
|
|
32
|
-
# @param data_store [Stoplight::Domain::DataStore]
|
|
33
|
-
# @param config [Stoplight::Domain::Config]
|
|
23
|
+
class CompatibilityState
|
|
34
24
|
def initialize(data_store:, config:)
|
|
35
25
|
@data_store = data_store
|
|
36
26
|
@config = config
|
|
37
27
|
end
|
|
38
28
|
|
|
39
|
-
# @return [Stoplight::Domain::StateSnapshot]
|
|
40
29
|
def state_snapshot = data_store.get_state_snapshot(config)
|
|
41
30
|
|
|
42
|
-
# @param state [String]
|
|
43
|
-
# @return [String]
|
|
44
31
|
def set_state(state) = data_store.set_state(config, state)
|
|
45
32
|
|
|
46
|
-
# @param color [String]
|
|
47
|
-
# @return [Boolean]
|
|
48
33
|
def transition_to_color(color) = data_store.transition_to_color(config, color)
|
|
49
34
|
|
|
50
|
-
# @return [void]
|
|
51
35
|
def clear = data_store.delete_light(config)
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :data_store
|
|
40
|
+
attr_reader :config
|
|
52
41
|
end
|
|
53
42
|
end
|
|
54
43
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
# Production clock implementation using Ruby's Time class.
|
|
6
|
+
#
|
|
7
|
+
# Default clock for all Stoplight time-dependent operations including
|
|
8
|
+
# bucket calculation, window boundaries, and state transition timestamps.
|
|
9
|
+
#
|
|
10
|
+
class SystemClock
|
|
11
|
+
def current_time = Time.now.utc
|
|
12
|
+
|
|
13
|
+
def at(timestamp) = Time.at(timestamp).utc
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "singleton"
|
|
4
|
+
|
|
5
|
+
module Stoplight
|
|
6
|
+
module Types
|
|
7
|
+
def self.undefined = Undefined.instance
|
|
8
|
+
|
|
9
|
+
# Asserts a value is non-nil, returning it with a narrowed type.
|
|
10
|
+
#
|
|
11
|
+
# Use this to satisfy Steep's flow typing when you know a nilable value
|
|
12
|
+
# must be present. Prefer this over type assertions (#: Type) since it
|
|
13
|
+
# provides runtime validation.
|
|
14
|
+
#
|
|
15
|
+
# @example Validating required configuration
|
|
16
|
+
# @window_size = T.must(config.window_size)
|
|
17
|
+
#
|
|
18
|
+
# @raise [TypeError] if value is nil
|
|
19
|
+
# @return [T] the non-nil value
|
|
20
|
+
#
|
|
21
|
+
def self.must(value)
|
|
22
|
+
if value.nil?
|
|
23
|
+
raise TypeError, "must not have nil value"
|
|
24
|
+
else
|
|
25
|
+
value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|