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,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module FailSafe
|
|
6
|
+
module Storage
|
|
7
|
+
# A wrapper around a store that provides fail-safe mechanisms using a
|
|
8
|
+
# circuit breaker. It ensures that operations on the store can gracefully
|
|
9
|
+
# handle failures by falling back to default values when necessary.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class Metrics
|
|
13
|
+
# The underlying primary store being used
|
|
14
|
+
attr_reader :primary_store
|
|
15
|
+
attr_reader :error_notifier
|
|
16
|
+
# The fallback store used when the primary fails.
|
|
17
|
+
attr_reader :failover_store
|
|
18
|
+
|
|
19
|
+
def initialize(primary_store:, error_notifier:, failover_store:, circuit_breaker:)
|
|
20
|
+
@primary_store = primary_store
|
|
21
|
+
@error_notifier = error_notifier
|
|
22
|
+
@failover_store = failover_store
|
|
23
|
+
@circuit_breaker = circuit_breaker
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def metrics_snapshot
|
|
27
|
+
circuit_breaker.run(fallback { failover_store.metrics_snapshot }) do
|
|
28
|
+
primary_store.metrics_snapshot
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def record_success
|
|
33
|
+
circuit_breaker.run(fallback { failover_store.record_success }) do
|
|
34
|
+
primary_store.record_success
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def record_failure(exception)
|
|
39
|
+
circuit_breaker.run(fallback { failover_store.record_failure(exception) }) do
|
|
40
|
+
primary_store.record_failure(exception)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def clear
|
|
45
|
+
circuit_breaker.run(fallback { failover_store.clear }) do
|
|
46
|
+
primary_store.clear
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
# The circuit breaker used to handle store failures.
|
|
53
|
+
attr_reader :circuit_breaker
|
|
54
|
+
|
|
55
|
+
def fallback(&fallback)
|
|
56
|
+
->(error) {
|
|
57
|
+
error_notifier.call(error) if error
|
|
58
|
+
fallback.call
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module Stoplight
|
|
6
|
+
module Infrastructure
|
|
7
|
+
module FailSafe
|
|
8
|
+
module Storage
|
|
9
|
+
# A wrapper around a store that provides fail-safe mechanisms using a
|
|
10
|
+
# circuit breaker. It ensures that operations on the store can gracefully
|
|
11
|
+
# handle failures by falling back to default values when necessary.
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
class RecoveryLock
|
|
15
|
+
# The underlying primary store being used
|
|
16
|
+
attr_reader :primary_store
|
|
17
|
+
attr_reader :error_notifier
|
|
18
|
+
# The fallback store used when the primary fails.
|
|
19
|
+
attr_reader :failover_store
|
|
20
|
+
|
|
21
|
+
def initialize(primary_store:, error_notifier:, failover_store:, circuit_breaker:)
|
|
22
|
+
@primary_store = primary_store
|
|
23
|
+
@error_notifier = error_notifier
|
|
24
|
+
@failover_store = failover_store
|
|
25
|
+
@circuit_breaker = circuit_breaker
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def acquire_lock
|
|
29
|
+
fallback = ->(error) {
|
|
30
|
+
error_notifier.call(error) if error
|
|
31
|
+
wrap_token(:failover, failover_store.acquire_lock)
|
|
32
|
+
}
|
|
33
|
+
circuit_breaker.run(fallback) do
|
|
34
|
+
wrap_token(:primary, primary_store.acquire_lock)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Routes release to correct store based on token type.
|
|
39
|
+
# Redis tokens release via primary (with error notification on failure).
|
|
40
|
+
# Memory tokens release via failover directly.
|
|
41
|
+
#
|
|
42
|
+
def release_lock(recovery_lock_token)
|
|
43
|
+
case recovery_lock_token.origin
|
|
44
|
+
in :primary
|
|
45
|
+
fallback = ->(error) {
|
|
46
|
+
error_notifier.call(error) if error
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
circuit_breaker.run(fallback) do
|
|
50
|
+
primary_store.release_lock(recovery_lock_token.underlying_token)
|
|
51
|
+
end
|
|
52
|
+
in :failover
|
|
53
|
+
failover_store.release_lock(recovery_lock_token.underlying_token)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# The circuit breaker used to handle store failures.
|
|
60
|
+
attr_reader :circuit_breaker
|
|
61
|
+
|
|
62
|
+
def wrap_token(origin, token)
|
|
63
|
+
RecoveryLockToken.new(origin:, underlying_token: token) if token
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module FailSafe
|
|
6
|
+
module Storage
|
|
7
|
+
class RecoveryLockToken
|
|
8
|
+
attr_reader :underlying_token
|
|
9
|
+
attr_reader :origin
|
|
10
|
+
|
|
11
|
+
def initialize(underlying_token:, origin:)
|
|
12
|
+
@underlying_token = underlying_token
|
|
13
|
+
@origin = origin
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module FailSafe
|
|
6
|
+
module Storage
|
|
7
|
+
# A wrapper around a store that provides fail-safe mechanisms using a
|
|
8
|
+
# circuit breaker. It ensures that operations on the store can gracefully
|
|
9
|
+
# handle failures by falling back to default values when necessary.
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class State
|
|
13
|
+
attr_reader :primary_store
|
|
14
|
+
attr_reader :error_notifier
|
|
15
|
+
attr_reader :failover_store
|
|
16
|
+
attr_reader :circuit_breaker
|
|
17
|
+
|
|
18
|
+
def initialize(primary_store:, error_notifier:, failover_store:, circuit_breaker:)
|
|
19
|
+
@primary_store = primary_store
|
|
20
|
+
@error_notifier = error_notifier
|
|
21
|
+
@failover_store = failover_store
|
|
22
|
+
@circuit_breaker = circuit_breaker
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def set_state(state)
|
|
26
|
+
circuit_breaker.run(fallback { failover_store.set_state(state) }) do
|
|
27
|
+
primary_store.set_state(state)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [Stoplight::Domain::StateSnapshot]
|
|
32
|
+
def state_snapshot
|
|
33
|
+
circuit_breaker.run(fallback { failover_store.state_snapshot }) do
|
|
34
|
+
primary_store.state_snapshot
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param color [String]
|
|
39
|
+
# @return [Boolean]
|
|
40
|
+
def transition_to_color(color)
|
|
41
|
+
circuit_breaker.run(fallback { failover_store.transition_to_color(color) }) do
|
|
42
|
+
primary_store.transition_to_color(color)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def clear
|
|
47
|
+
circuit_breaker.run(fallback { failover_store.clear }) do
|
|
48
|
+
primary_store.clear
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private def fallback(&fallback)
|
|
53
|
+
->(error) {
|
|
54
|
+
error_notifier.call(error) if error
|
|
55
|
+
fallback.call
|
|
56
|
+
}
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_store.rb
RENAMED
|
@@ -4,8 +4,8 @@ require "concurrent/map"
|
|
|
4
4
|
|
|
5
5
|
module Stoplight
|
|
6
6
|
module Infrastructure
|
|
7
|
-
module
|
|
8
|
-
class
|
|
7
|
+
module Memory
|
|
8
|
+
class DataStore
|
|
9
9
|
# Process-local recovery lock using Ruby's Thread::Mutex.
|
|
10
10
|
#
|
|
11
11
|
# This only serializes recovery within a single Ruby process.
|
|
@@ -17,31 +17,29 @@ module Stoplight
|
|
|
17
17
|
# - Mutexes persist for process lifetime (never GC'd)
|
|
18
18
|
#
|
|
19
19
|
class RecoveryLockStore
|
|
20
|
-
# @!attribute locks
|
|
21
|
-
# Stores one mutex per unique light_name for the lifetime of the process.
|
|
22
|
-
# Mutexes are never garbage collected.
|
|
23
|
-
# @return [Concurrent::Map<Thread::Mutex>]
|
|
24
|
-
private attr_reader :locks
|
|
25
|
-
|
|
26
20
|
def initialize
|
|
27
21
|
@locks = Concurrent::Map.new
|
|
28
22
|
end
|
|
29
23
|
|
|
30
24
|
# @param light_name [String]
|
|
31
|
-
# @return [Stoplight::Infrastructure::DataStore::
|
|
25
|
+
# @return [Stoplight::Infrastructure::Memory::DataStore::RecoveryLockToken, nil]
|
|
32
26
|
def acquire_lock(light_name)
|
|
33
27
|
lock = lock_for(light_name)
|
|
34
28
|
RecoveryLockToken.new(light_name:) if lock.try_lock
|
|
35
29
|
end
|
|
36
30
|
|
|
37
|
-
# @param recovery_lock_token [Stoplight::Infrastructure::DataStore::
|
|
31
|
+
# @param recovery_lock_token [Stoplight::Infrastructure::Memory::DataStore::RecoveryLockToken]
|
|
38
32
|
# @return [void]
|
|
39
33
|
def release_lock(recovery_lock_token)
|
|
40
34
|
lock_for(recovery_lock_token.light_name).unlock
|
|
41
35
|
end
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
# Stores one mutex per unique light_name for the lifetime of the process.
|
|
40
|
+
# Mutexes are never garbage collected.
|
|
41
|
+
attr_reader :locks
|
|
42
|
+
|
|
45
43
|
private def lock_for(light_name)
|
|
46
44
|
locks.compute_if_absent(light_name) do
|
|
47
45
|
Thread::Mutex.new
|
data/lib/stoplight/infrastructure/{data_store/memory → memory/data_store}/recovery_lock_token.rb
RENAMED
|
@@ -2,14 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
module Stoplight
|
|
4
4
|
module Infrastructure
|
|
5
|
-
module
|
|
6
|
-
class
|
|
7
|
-
class RecoveryLockToken
|
|
8
|
-
# @!attribute light_name
|
|
9
|
-
# @return [String]
|
|
5
|
+
module Memory
|
|
6
|
+
class DataStore
|
|
7
|
+
class RecoveryLockToken
|
|
10
8
|
attr_reader :light_name
|
|
11
9
|
|
|
12
|
-
# @param light_name [String]
|
|
13
10
|
def initialize(light_name:)
|
|
14
11
|
@light_name = light_name
|
|
15
12
|
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Stoplight
|
|
4
4
|
module Infrastructure
|
|
5
|
-
module
|
|
6
|
-
class
|
|
5
|
+
module Memory
|
|
6
|
+
class DataStore
|
|
7
7
|
# Hash-based sliding window for O(1) amortized operations.
|
|
8
8
|
#
|
|
9
9
|
# Maintains a running sum and stores per-second counts in a Hash. Ruby's Hash
|
|
@@ -17,17 +17,10 @@ module Stoplight
|
|
|
17
17
|
# @note Not thread-safe; synchronization must be handled externally
|
|
18
18
|
# @api private
|
|
19
19
|
class SlidingWindow
|
|
20
|
-
|
|
21
|
-
# @return [Hash<Integer, Integer>] A hash mapping time buckets to their counts
|
|
22
|
-
private attr_reader :buckets
|
|
23
|
-
|
|
24
|
-
# @!attribute running_sum
|
|
25
|
-
# @return [Integer] The running sum of all increments in the current window
|
|
26
|
-
private attr_accessor :running_sum
|
|
27
|
-
|
|
28
|
-
def initialize
|
|
20
|
+
def initialize(clock:)
|
|
29
21
|
@buckets = Hash.new { |buckets, bucket| buckets[bucket] = 0 }
|
|
30
22
|
@running_sum = 0
|
|
23
|
+
@clock = clock
|
|
31
24
|
end
|
|
32
25
|
|
|
33
26
|
# Increment the count at a given timestamp
|
|
@@ -36,14 +29,24 @@ module Stoplight
|
|
|
36
29
|
self.running_sum += 1
|
|
37
30
|
end
|
|
38
31
|
|
|
39
|
-
# @param window_start [Time]
|
|
40
|
-
# @return [Integer]
|
|
41
32
|
def sum_in_window(window_start)
|
|
42
33
|
slide_window!(window_start)
|
|
43
34
|
self.running_sum
|
|
44
35
|
end
|
|
45
36
|
|
|
46
|
-
|
|
37
|
+
def inspect
|
|
38
|
+
"#<#{self.class.name} #{buckets}>"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# A hash mapping time buckets to their counts
|
|
44
|
+
attr_reader :buckets
|
|
45
|
+
# The running sum of all increments in the current window
|
|
46
|
+
attr_accessor :running_sum
|
|
47
|
+
attr_reader :clock
|
|
48
|
+
|
|
49
|
+
def slide_window!(window_start)
|
|
47
50
|
window_start_ts = window_start.to_i
|
|
48
51
|
|
|
49
52
|
loop do
|
|
@@ -51,27 +54,19 @@ module Stoplight
|
|
|
51
54
|
if timestamp.nil? || timestamp >= window_start_ts
|
|
52
55
|
break
|
|
53
56
|
else
|
|
54
|
-
self.running_sum -= sum
|
|
57
|
+
self.running_sum -= sum.to_i
|
|
55
58
|
buckets.shift
|
|
56
59
|
end
|
|
57
60
|
end
|
|
58
61
|
end
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
bucket_for_time(current_time)
|
|
63
|
+
def current_bucket
|
|
64
|
+
bucket_for_time(clock.current_time)
|
|
62
65
|
end
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
def bucket_for_time(time)
|
|
65
68
|
time.to_i
|
|
66
69
|
end
|
|
67
|
-
|
|
68
|
-
private def current_time
|
|
69
|
-
Time.now
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def inspect
|
|
73
|
-
"#<#{self.class.name} #{buckets}>"
|
|
74
|
-
end
|
|
75
70
|
end
|
|
76
71
|
end
|
|
77
72
|
end
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Stoplight
|
|
4
4
|
module Infrastructure
|
|
5
|
-
module
|
|
6
|
-
class
|
|
5
|
+
module Memory
|
|
6
|
+
class DataStore
|
|
7
7
|
class State
|
|
8
8
|
attr_accessor :recovered_at
|
|
9
9
|
attr_accessor :locked_state
|
|
@@ -12,7 +12,7 @@ module Stoplight
|
|
|
12
12
|
attr_accessor :breached_at
|
|
13
13
|
|
|
14
14
|
def initialize
|
|
15
|
-
@locked_state =
|
|
15
|
+
@locked_state = Stoplight::State::UNLOCKED
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
end
|
|
@@ -4,23 +4,30 @@ require "monitor"
|
|
|
4
4
|
|
|
5
5
|
module Stoplight
|
|
6
6
|
module Infrastructure
|
|
7
|
-
module
|
|
8
|
-
#
|
|
9
|
-
|
|
7
|
+
module Memory
|
|
8
|
+
# steep:ignore:start
|
|
9
|
+
# @see +Domain::_DataStore+
|
|
10
|
+
class DataStore
|
|
10
11
|
include MonitorMixin
|
|
11
12
|
|
|
12
13
|
KEY_SEPARATOR = ":"
|
|
13
14
|
|
|
14
15
|
# @!attribute recovery_lock_store
|
|
15
|
-
# @return [Stoplight::Infrastructure::DataStore::
|
|
16
|
+
# @return [Stoplight::Infrastructure::Memory::DataStore::RecoveryLockStore]
|
|
16
17
|
# @api private
|
|
17
18
|
private attr_reader :recovery_lock_store
|
|
18
19
|
|
|
19
|
-
#
|
|
20
|
-
|
|
20
|
+
# @!attribute clock
|
|
21
|
+
# @return [Stoplight::Domain::_Clock]
|
|
22
|
+
private attr_reader :clock
|
|
23
|
+
|
|
24
|
+
# @param recovery_lock_store [Stoplight::Infrastructure::Memory::DataStore::RecoveryLockStore]
|
|
25
|
+
# @param clock [Stoplight::Domain::_Clock]
|
|
26
|
+
def initialize(recovery_lock_store:, clock:)
|
|
27
|
+
@clock = clock
|
|
21
28
|
@recovery_lock_store = recovery_lock_store
|
|
22
|
-
@errors = Hash.new { |errors, light_name| errors[light_name] = SlidingWindow.new }
|
|
23
|
-
@successes = Hash.new { |successes, light_name| successes[light_name] = SlidingWindow.new }
|
|
29
|
+
@errors = Hash.new { |errors, light_name| errors[light_name] = SlidingWindow.new(clock:) }
|
|
30
|
+
@successes = Hash.new { |successes, light_name| successes[light_name] = SlidingWindow.new(clock:) }
|
|
24
31
|
@metrics = Hash.new { |metrics, light_name| metrics[light_name] = Metrics.new }
|
|
25
32
|
|
|
26
33
|
@recovery_metrics = Hash.new { |metrics, light_name| metrics[light_name] = Metrics.new }
|
|
@@ -36,12 +43,12 @@ module Stoplight
|
|
|
36
43
|
end
|
|
37
44
|
|
|
38
45
|
# @param config [Stoplight::Domain::Config]
|
|
39
|
-
# @return [Stoplight::Domain::
|
|
46
|
+
# @return [Stoplight::Domain::MetricsSnapshot]
|
|
40
47
|
def get_metrics(config)
|
|
41
48
|
light_name = config.name
|
|
42
49
|
|
|
43
50
|
synchronize do
|
|
44
|
-
current_time =
|
|
51
|
+
current_time = clock.current_time
|
|
45
52
|
window_start = if config.window_size
|
|
46
53
|
(current_time - config.window_size)
|
|
47
54
|
else
|
|
@@ -55,7 +62,7 @@ module Stoplight
|
|
|
55
62
|
consecutive_errors = config.window_size ? [metrics.consecutive_errors, errors].min : metrics.consecutive_errors
|
|
56
63
|
consecutive_successes = config.window_size ? [metrics.consecutive_successes.to_i, successes].min : metrics.consecutive_successes.to_i
|
|
57
64
|
|
|
58
|
-
Domain::
|
|
65
|
+
Domain::MetricsSnapshot.new(
|
|
59
66
|
errors:,
|
|
60
67
|
successes:,
|
|
61
68
|
consecutive_errors:,
|
|
@@ -66,14 +73,14 @@ module Stoplight
|
|
|
66
73
|
end
|
|
67
74
|
end
|
|
68
75
|
|
|
69
|
-
# @return [Stoplight::Domain::
|
|
76
|
+
# @return [Stoplight::Domain::MetricsSnapshot]
|
|
70
77
|
def get_recovery_metrics(config)
|
|
71
78
|
light_name = config.name
|
|
72
79
|
|
|
73
80
|
synchronize do
|
|
74
81
|
metrics = @recovery_metrics[light_name]
|
|
75
82
|
|
|
76
|
-
Domain::
|
|
83
|
+
Domain::MetricsSnapshot.new(
|
|
77
84
|
errors: nil, successes: nil,
|
|
78
85
|
consecutive_errors: metrics.consecutive_errors,
|
|
79
86
|
consecutive_successes: metrics.consecutive_successes,
|
|
@@ -86,7 +93,7 @@ module Stoplight
|
|
|
86
93
|
# @return [Stoplight::Domain::StateSnapshot]
|
|
87
94
|
def get_state_snapshot(config)
|
|
88
95
|
time, state = synchronize do
|
|
89
|
-
[current_time, @states[config.name]]
|
|
96
|
+
[clock.current_time, @states[config.name]]
|
|
90
97
|
end
|
|
91
98
|
|
|
92
99
|
Domain::StateSnapshot.new(
|
|
@@ -102,7 +109,7 @@ module Stoplight
|
|
|
102
109
|
# @param exception [Exception]
|
|
103
110
|
# @return [void]
|
|
104
111
|
def record_failure(config, exception)
|
|
105
|
-
current_time =
|
|
112
|
+
current_time = clock.current_time
|
|
106
113
|
light_name = config.name
|
|
107
114
|
failure = Domain::Failure.from_error(exception, time: current_time)
|
|
108
115
|
|
|
@@ -124,8 +131,8 @@ module Stoplight
|
|
|
124
131
|
light_name = config.name
|
|
125
132
|
synchronize do
|
|
126
133
|
if config.window_size
|
|
127
|
-
@errors[light_name] = SlidingWindow.new
|
|
128
|
-
@successes[light_name] = SlidingWindow.new
|
|
134
|
+
@errors[light_name] = SlidingWindow.new(clock:)
|
|
135
|
+
@successes[light_name] = SlidingWindow.new(clock:)
|
|
129
136
|
end
|
|
130
137
|
@metrics[light_name] = Metrics.new
|
|
131
138
|
end
|
|
@@ -141,7 +148,7 @@ module Stoplight
|
|
|
141
148
|
# @return [void]
|
|
142
149
|
def record_success(config)
|
|
143
150
|
light_name = config.name
|
|
144
|
-
current_time =
|
|
151
|
+
current_time = clock.current_time
|
|
145
152
|
|
|
146
153
|
synchronize do
|
|
147
154
|
@successes[light_name].increment if config.window_size
|
|
@@ -162,7 +169,7 @@ module Stoplight
|
|
|
162
169
|
# @return [void]
|
|
163
170
|
def record_recovery_probe_failure(config, exception)
|
|
164
171
|
light_name = config.name
|
|
165
|
-
current_time =
|
|
172
|
+
current_time = clock.current_time
|
|
166
173
|
failure = Domain::Failure.from_error(exception, time: current_time)
|
|
167
174
|
|
|
168
175
|
synchronize do
|
|
@@ -181,7 +188,7 @@ module Stoplight
|
|
|
181
188
|
# @return [void]
|
|
182
189
|
def record_recovery_probe_success(config)
|
|
183
190
|
light_name = config.name
|
|
184
|
-
current_time =
|
|
191
|
+
current_time = clock.current_time
|
|
185
192
|
|
|
186
193
|
synchronize do
|
|
187
194
|
metrics = @recovery_metrics[light_name]
|
|
@@ -232,11 +239,11 @@ module Stoplight
|
|
|
232
239
|
# @return [Boolean] true if this is the first instance to detect this transition
|
|
233
240
|
def transition_to_color(config, color)
|
|
234
241
|
case color
|
|
235
|
-
when
|
|
242
|
+
when Color::GREEN
|
|
236
243
|
transition_to_green(config)
|
|
237
|
-
when
|
|
244
|
+
when Color::YELLOW
|
|
238
245
|
transition_to_yellow(config)
|
|
239
|
-
when
|
|
246
|
+
when Color::RED
|
|
240
247
|
transition_to_red(config)
|
|
241
248
|
else
|
|
242
249
|
raise ArgumentError, "Invalid color: #{color}"
|
|
@@ -244,12 +251,12 @@ module Stoplight
|
|
|
244
251
|
end
|
|
245
252
|
|
|
246
253
|
# @param config [Stoplight::Domain::Config]
|
|
247
|
-
# @return [Stoplight::Infrastructure::DataStore::
|
|
254
|
+
# @return [Stoplight::Infrastructure::Memory::DataStore::RecoveryLockToken, nil]
|
|
248
255
|
def acquire_recovery_lock(config)
|
|
249
256
|
recovery_lock_store.acquire_lock(config.name)
|
|
250
257
|
end
|
|
251
258
|
|
|
252
|
-
# @param lock [Stoplight::Infrastructure::DataStore::
|
|
259
|
+
# @param lock [Stoplight::Infrastructure::Memory::DataStore::RecoveryLockToken]
|
|
253
260
|
# @return [void]
|
|
254
261
|
def release_recovery_lock(lock)
|
|
255
262
|
recovery_lock_store.release_lock(lock)
|
|
@@ -261,7 +268,7 @@ module Stoplight
|
|
|
261
268
|
# @return [Boolean] true if this is the first instance to detect this transition
|
|
262
269
|
private def transition_to_green(config)
|
|
263
270
|
light_name = config.name
|
|
264
|
-
current_time =
|
|
271
|
+
current_time = clock.current_time
|
|
265
272
|
|
|
266
273
|
synchronize do
|
|
267
274
|
state = @states[light_name]
|
|
@@ -284,7 +291,7 @@ module Stoplight
|
|
|
284
291
|
# @return [Boolean] true if this is the first instance to detect this transition
|
|
285
292
|
private def transition_to_yellow(config)
|
|
286
293
|
light_name = config.name
|
|
287
|
-
current_time =
|
|
294
|
+
current_time = clock.current_time
|
|
288
295
|
|
|
289
296
|
synchronize do
|
|
290
297
|
state = @states[light_name]
|
|
@@ -309,7 +316,7 @@ module Stoplight
|
|
|
309
316
|
# @return [Boolean] true if this is the first instance to detect this transition
|
|
310
317
|
private def transition_to_red(config)
|
|
311
318
|
light_name = config.name
|
|
312
|
-
current_time =
|
|
319
|
+
current_time = clock.current_time
|
|
313
320
|
recovery_scheduled_after = current_time + config.cool_off_time
|
|
314
321
|
|
|
315
322
|
synchronize do
|
|
@@ -328,11 +335,8 @@ module Stoplight
|
|
|
328
335
|
end
|
|
329
336
|
end
|
|
330
337
|
end
|
|
331
|
-
|
|
332
|
-
private def current_time
|
|
333
|
-
Time.now
|
|
334
|
-
end
|
|
335
338
|
end
|
|
339
|
+
# steep:ignore:end
|
|
336
340
|
end
|
|
337
341
|
end
|
|
338
342
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Memory
|
|
6
|
+
module Storage
|
|
7
|
+
# Process-local recovery lock using Ruby's Thread::Mutex.
|
|
8
|
+
#
|
|
9
|
+
# This only serializes recovery within a single Ruby process.
|
|
10
|
+
# Multiple processes/servers will NOT coordinate - each process
|
|
11
|
+
# can send probes independently.
|
|
12
|
+
#
|
|
13
|
+
class RecoveryLock
|
|
14
|
+
def initialize
|
|
15
|
+
@lock = Thread::Mutex.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def acquire_lock
|
|
19
|
+
if lock.try_lock
|
|
20
|
+
Domain::Storage::RecoveryLockToken.new
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def release_lock(_recovery_lock_token)
|
|
25
|
+
lock.unlock
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
attr_reader :lock
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Memory
|
|
6
|
+
module Storage
|
|
7
|
+
# When a circuit is RED (open), Stoplight periodically sends "recovery probes"
|
|
8
|
+
# to test whether the protected service has recovered. These test requests have
|
|
9
|
+
# different semantics than normal requests and their metrics are tracked separately.
|
|
10
|
+
#
|
|
11
|
+
class RecoveryMetrics < UnboundedMetrics
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|