stoplight 5.8.3 → 6.0.0.rc2
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/CHANGELOG.md +1 -1
- data/LICENSE.md +1 -0
- data/README.md +132 -79
- data/UPGRADING.md +265 -6
- data/lib/stoplight/admin/actions/action.rb +4 -12
- data/lib/stoplight/admin/actions/lock.rb +12 -9
- data/lib/stoplight/admin/actions/lock_all.rb +27 -0
- data/lib/stoplight/admin/actions/remove.rb +12 -9
- data/lib/stoplight/admin/actions/stats.rb +15 -12
- data/lib/stoplight/admin/actions/unlock.rb +10 -9
- data/lib/stoplight/admin/assets/apple-touch-icon.png +0 -0
- data/lib/stoplight/admin/assets/favicon.ico +0 -0
- data/lib/stoplight/admin/assets/flowbite.min.css +2 -0
- data/lib/stoplight/admin/assets/flowbite.min.js +1 -0
- data/lib/stoplight/admin/assets/icon.svg +7 -0
- data/lib/stoplight/admin/assets/turbo.es2017-esm.min.js +34 -0
- data/lib/stoplight/admin/config_registry.rb +40 -0
- data/lib/stoplight/admin/dependencies.rb +29 -28
- data/lib/stoplight/admin/helpers.rb +50 -17
- data/lib/stoplight/admin/light_view.rb +184 -0
- data/lib/stoplight/admin/lights_stats.rb +7 -9
- data/lib/stoplight/admin/views/_card.erb +19 -21
- data/lib/stoplight/admin/views/index.erb +9 -3
- data/lib/stoplight/admin/views/layout.erb +45 -13
- data/lib/stoplight/admin.rb +110 -21
- data/lib/stoplight/data_store.rb +18 -8
- data/lib/stoplight/domain/config.rb +4 -1
- data/lib/stoplight/domain/error_tracking_policy.rb +9 -0
- data/lib/stoplight/domain/failure.rb +4 -0
- data/lib/stoplight/domain/id.rb +13 -0
- data/lib/stoplight/domain/light.rb +35 -77
- data/lib/stoplight/domain/lock_control.rb +55 -0
- data/lib/stoplight/domain/matcher_validator.rb +24 -0
- data/lib/stoplight/domain/state_snapshot.rb +2 -6
- data/lib/stoplight/domain/strategies/green_run_strategy.rb +33 -19
- data/lib/stoplight/domain/strategies/red_run_strategy.rb +8 -2
- data/lib/stoplight/domain/strategies/yellow_run_strategy.rb +56 -32
- data/lib/stoplight/domain/telemetry/bus.rb +90 -0
- data/lib/stoplight/domain/telemetry/consumer.rb +18 -0
- data/lib/stoplight/domain/telemetry/emitter.rb +45 -0
- data/lib/stoplight/domain/telemetry/envelope.rb +15 -0
- data/lib/stoplight/domain/telemetry/failure.rb +13 -0
- data/lib/stoplight/domain/telemetry/light_registered.rb +12 -0
- data/lib/stoplight/domain/telemetry/lock_changed.rb +19 -0
- data/lib/stoplight/domain/telemetry/metrics.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_failed.rb +20 -0
- data/lib/stoplight/domain/telemetry/recovery_probe_completed.rb +15 -0
- data/lib/stoplight/domain/telemetry/recovery_started.rb +18 -0
- data/lib/stoplight/domain/telemetry/recovery_succeeded.rb +19 -0
- data/lib/stoplight/domain/telemetry/run_completed.rb +17 -0
- data/lib/stoplight/domain/telemetry/run_recorder.rb +58 -0
- data/lib/stoplight/domain/telemetry/settings.rb +21 -0
- data/lib/stoplight/domain/telemetry/state_transitioned.rb +11 -0
- data/lib/stoplight/domain/telemetry/subscription.rb +11 -0
- data/lib/stoplight/domain/telemetry/traffic_breached.rb +20 -0
- data/lib/stoplight/domain/tracker/recovery_probe.rb +61 -17
- data/lib/stoplight/domain/tracker/request.rb +17 -8
- data/lib/stoplight/domain/traffic_control/consecutive_errors.rb +13 -3
- data/lib/stoplight/domain/traffic_control/error_rate.rb +16 -12
- data/lib/stoplight/domain/traffic_recovery/consecutive_successes.rb +9 -3
- data/lib/stoplight/error.rb +10 -1
- data/lib/stoplight/infrastructure/config_serializer.rb +50 -0
- data/lib/stoplight/infrastructure/fail_safe/error_notifier.rb +25 -0
- data/lib/stoplight/infrastructure/fail_safe/storage/registry.rb +68 -0
- data/lib/stoplight/infrastructure/memory/storage/registry.rb +18 -0
- data/lib/stoplight/infrastructure/memory/storage/unbounded_metrics.rb +29 -32
- data/lib/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rb +74 -0
- data/lib/stoplight/infrastructure/memory/storage/window_metrics.rb +25 -24
- data/lib/stoplight/infrastructure/notifier/fail_safe.rb +3 -8
- data/lib/stoplight/infrastructure/notifier/generic.rb +3 -3
- data/lib/stoplight/infrastructure/redis/key.rb +25 -0
- data/lib/stoplight/infrastructure/redis/storage/now.lua +9 -0
- data/lib/stoplight/infrastructure/redis/storage/recovery_lock.rb +2 -4
- data/lib/stoplight/infrastructure/redis/storage/recovery_metrics.rb +1 -1
- data/lib/stoplight/infrastructure/redis/storage/registry.rb +73 -0
- data/lib/stoplight/infrastructure/redis/storage/scripting.rb +54 -3
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_green.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_red.lua +4 -2
- data/lib/stoplight/infrastructure/redis/storage/state/transition_to_yellow.lua +3 -1
- data/lib/stoplight/infrastructure/redis/storage/state.rb +8 -8
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_failure.lua +20 -21
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics/record_success.lua +11 -19
- data/lib/stoplight/infrastructure/redis/storage/unbounded_metrics.rb +20 -16
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/_evict.lua +44 -0
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/metrics_snapshot.lua +14 -21
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_failure.lua +38 -36
- data/lib/stoplight/infrastructure/redis/storage/window_metrics/record_success.lua +23 -30
- data/lib/stoplight/infrastructure/redis/storage/window_metrics.rb +45 -135
- data/lib/stoplight/infrastructure/system_clock.rb +4 -0
- data/lib/stoplight/telemetry.rb +19 -0
- data/lib/stoplight/types.rb +15 -2
- data/lib/stoplight/version.rb +1 -1
- data/lib/stoplight/wiring/config_compatibility_validator.rb +25 -2
- data/lib/stoplight/wiring/default.rb +1 -1
- data/lib/stoplight/wiring/default_config.rb +2 -1
- data/lib/stoplight/wiring/default_configuration.rb +3 -2
- data/lib/stoplight/wiring/external_caller.rb +27 -0
- data/lib/stoplight/wiring/fail_safe_config.rb +11 -0
- data/lib/stoplight/wiring/global_state.rb +93 -0
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_control_dsl.rb +3 -5
- data/lib/stoplight/wiring/{light_factory → light_configuration_dsl}/traffic_recovery_dsl.rb +2 -2
- data/lib/stoplight/wiring/light_configuration_dsl.rb +97 -0
- data/lib/stoplight/wiring/light_factory.rb +154 -97
- data/lib/stoplight/wiring/notifier_bridge.rb +33 -0
- data/lib/stoplight/wiring/redis/backend.rb +1 -1
- data/lib/stoplight/wiring/system/storage.rb +69 -0
- data/lib/stoplight/wiring/system.rb +125 -56
- data/lib/stoplight/wiring/{configuration_dsl.rb → system_configuration_dsl.rb} +13 -43
- data/lib/stoplight.rb +50 -194
- data/sig/sinatra/base.rbs +6 -0
- data/sig/stdlib/securerandom.rbs +4 -0
- data/sig/stoplight/admin/actions/action.rbs +10 -0
- data/sig/stoplight/admin/actions/lock.rbs +17 -0
- data/sig/stoplight/admin/actions/lock_all.rbs +17 -0
- data/sig/stoplight/admin/actions/remove.rbs +22 -0
- data/sig/stoplight/admin/actions/stats.rbs +28 -0
- data/sig/stoplight/admin/actions/unlock.rbs +17 -0
- data/sig/stoplight/admin/config_registry.rbs +20 -0
- data/sig/stoplight/admin/dependencies.rbs +16 -0
- data/sig/stoplight/admin/helpers.rbs +17 -0
- data/sig/stoplight/admin/light_view.rbs +39 -0
- data/sig/stoplight/admin/lights_stats.rbs +30 -0
- data/sig/stoplight/admin.rbs +14 -0
- data/sig/stoplight/color.rbs +3 -3
- data/sig/{_private/stoplight/data_store.rbs → stoplight/data_store/base.rbs} +1 -0
- data/sig/stoplight/data_store/memory.rbs +9 -0
- data/sig/stoplight/data_store/redis.rbs +17 -0
- data/sig/{_private/stoplight → stoplight}/domain/config.rbs +28 -24
- data/sig/{_private/stoplight → stoplight}/domain/error_tracking_policy.rbs +4 -0
- data/sig/{_private/stoplight → stoplight}/domain/failure.rbs +2 -0
- data/sig/stoplight/domain/id.rbs +7 -0
- data/sig/stoplight/domain/light.rbs +39 -0
- data/sig/{_private/stoplight → stoplight}/domain/light_info.rbs +1 -5
- data/sig/stoplight/domain/lock_control.rbs +16 -0
- data/sig/stoplight/domain/matcher_validator.rbs +11 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/clock.rbs +6 -0
- data/sig/stoplight/domain/ports/data_store.rbs +9 -0
- data/sig/stoplight/domain/ports/error_notifier.rbs +12 -0
- data/sig/stoplight/domain/ports/exception_matcher.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/metrics_store.rbs +2 -2
- data/sig/stoplight/domain/ports/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/run_strategy.rbs +2 -1
- data/sig/stoplight/domain/ports/state_transition_notifier.rbs +18 -0
- data/sig/stoplight/domain/ports/telemetry.rbs +28 -0
- data/sig/stoplight/domain/ports/telemetry_publisher.rbs +14 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_control.rbs +7 -0
- data/sig/{_private/stoplight → stoplight}/domain/ports/traffic_recovery.rbs +2 -3
- data/sig/stoplight/domain/strategies/green_run_strategy.rbs +22 -0
- data/sig/stoplight/domain/strategies/red_run_strategy.rbs +19 -0
- data/sig/{_private/stoplight → stoplight}/domain/strategies/yellow_run_strategy.rbs +23 -12
- data/sig/stoplight/domain/telemetry/bus.rbs +49 -0
- data/sig/stoplight/domain/telemetry/consumer.rbs +13 -0
- data/sig/stoplight/domain/telemetry/emitter.rbs +51 -0
- data/sig/stoplight/domain/telemetry/envelope.rbs +30 -0
- data/sig/stoplight/domain/telemetry/failure.rbs +23 -0
- data/sig/stoplight/domain/telemetry/light_registered.rbs +23 -0
- data/sig/stoplight/domain/telemetry/lock_changed.rbs +31 -0
- data/sig/stoplight/domain/telemetry/metrics.rbs +27 -0
- data/sig/stoplight/domain/telemetry/recovery_failed.rbs +36 -0
- data/sig/stoplight/domain/telemetry/recovery_probe_completed.rbs +29 -0
- data/sig/stoplight/domain/telemetry/recovery_started.rbs +26 -0
- data/sig/stoplight/domain/telemetry/recovery_succeeded.rbs +31 -0
- data/sig/stoplight/domain/telemetry/run_completed.rbs +38 -0
- data/sig/stoplight/domain/telemetry/run_recorder.rbs +27 -0
- data/sig/stoplight/domain/telemetry/settings.rbs +47 -0
- data/sig/stoplight/domain/telemetry/state_transitioned.rbs +11 -0
- data/sig/stoplight/domain/telemetry/subscription.rbs +9 -0
- data/sig/stoplight/domain/telemetry/traffic_breached.rbs +36 -0
- data/sig/stoplight/domain/telemetry/types.rbs +32 -0
- data/sig/{_private/stoplight → stoplight}/domain/tracker/recovery_probe.rbs +10 -6
- data/sig/{_private/stoplight → stoplight}/domain/tracker/request.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/consecutive_errors.rbs +2 -0
- data/sig/{_private/stoplight → stoplight}/domain/traffic_control/error_rate.rbs +3 -2
- data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery/consecutive_successes.rbs +2 -0
- data/sig/stoplight/domain/types.rbs +25 -0
- data/sig/stoplight/error.rbs +8 -0
- data/sig/stoplight/generators/rails_generators_base.rbs +11 -0
- data/sig/stoplight/infrastructure/config_serializer.rbs +12 -0
- data/sig/stoplight/infrastructure/fail_safe/error_notifier.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/metrics.rbs +2 -2
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/fail_safe/storage/registry.rbs +23 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/state.rbs +4 -4
- data/sig/stoplight/infrastructure/memory/storage/registry.rbs +13 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/state.rbs +3 -3
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/unbounded_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/memory/storage/window_metrics/sliding_window.rbs +27 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/window_metrics.rbs +7 -7
- data/sig/stoplight/infrastructure/notifier/fail_safe.rbs +15 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/generic.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/io.rbs +1 -1
- data/sig/{_private/stoplight → stoplight}/infrastructure/notifier/logger.rbs +1 -1
- data/sig/stoplight/infrastructure/redis/key.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_lock.rbs +2 -2
- data/sig/stoplight/infrastructure/redis/storage/registry.rbs +27 -0
- data/sig/stoplight/infrastructure/redis/storage/scripting.rbs +34 -0
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/state.rbs +4 -4
- data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/unbounded_metrics.rbs +8 -1
- data/sig/stoplight/infrastructure/redis/storage/window_metrics.rbs +35 -0
- data/sig/stoplight/infrastructure/types.rbs +6 -0
- data/sig/stoplight/notifier.rbs +1 -1
- data/sig/stoplight/ports/system.rbs +16 -10
- data/sig/stoplight/state.rbs +3 -3
- data/sig/stoplight/telemetry.rbs +17 -0
- data/sig/stoplight/types.rbs +9 -0
- data/sig/{_private/stoplight → stoplight}/wiring/config_compatibility_validator.rbs +2 -0
- data/sig/stoplight/wiring/default.rbs +26 -0
- data/sig/stoplight/wiring/default_configuration.rbs +39 -0
- data/sig/stoplight/wiring/external_caller.rbs +13 -0
- data/sig/stoplight/wiring/fail_safe_config.rbs +7 -0
- data/sig/stoplight/wiring/global_state.rbs +36 -0
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_control_dsl.rbs +1 -1
- data/sig/{_private/stoplight/wiring/light_factory → stoplight/wiring/light_configuration_dsl}/traffic_recovery_dsl.rbs +1 -1
- data/sig/stoplight/wiring/light_configuration_dsl.rbs +38 -0
- data/sig/stoplight/wiring/light_factory.rbs +78 -0
- data/sig/stoplight/wiring/notifier_bridge.rbs +15 -0
- data/sig/stoplight/wiring/notifier_factory.rbs +10 -0
- data/sig/{_private/stoplight → stoplight}/wiring/redis/backend.rbs +6 -6
- data/sig/stoplight/wiring/system/storage.rbs +35 -0
- data/sig/stoplight/wiring/system.rbs +30 -0
- data/sig/stoplight/wiring/system_configuration_dsl.rbs +39 -0
- data/sig/stoplight/wiring/types.rbs +6 -0
- data/sig/stoplight.rbs +37 -54
- metadata +190 -143
- data/lib/stoplight/admin/actions/lock_all_green.rb +0 -18
- data/lib/stoplight/admin/actions/lock_green.rb +0 -23
- data/lib/stoplight/admin/actions/lock_red.rb +0 -23
- data/lib/stoplight/admin/lights_repository/light.rb +0 -171
- data/lib/stoplight/admin/lights_repository.rb +0 -87
- data/lib/stoplight/domain/light/configuration_builder_interface.rb +0 -236
- data/lib/stoplight/infrastructure/fail_safe/data_store.rb +0 -152
- data/lib/stoplight/infrastructure/memory/data_store/metrics.rb +0 -27
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_store.rb +0 -52
- data/lib/stoplight/infrastructure/memory/data_store/recovery_lock_token.rb +0 -17
- data/lib/stoplight/infrastructure/memory/data_store/sliding_window.rb +0 -74
- data/lib/stoplight/infrastructure/memory/data_store/state.rb +0 -21
- data/lib/stoplight/infrastructure/memory/data_store.rb +0 -342
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/get_metrics.lua +0 -26
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_failure.lua +0 -36
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_failure.lua +0 -27
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_recovery_probe_success.lua +0 -23
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/record_success.lua +0 -35
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/release_lock.lua +0 -6
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_green.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_red.lua +0 -10
- data/lib/stoplight/infrastructure/redis/data_store/lua_scripts/transition_to_yellow.lua +0 -9
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_store.rb +0 -60
- data/lib/stoplight/infrastructure/redis/data_store/recovery_lock_token.rb +0 -28
- data/lib/stoplight/infrastructure/redis/data_store/scripting.rb +0 -73
- data/lib/stoplight/infrastructure/redis/data_store.rb +0 -516
- data/lib/stoplight/infrastructure/redis/storage/key_space.rb +0 -51
- data/lib/stoplight/infrastructure/storage/compatibility_metrics.rb +0 -41
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_lock.rb +0 -33
- data/lib/stoplight/infrastructure/storage/compatibility_recovery_metrics.rb +0 -47
- data/lib/stoplight/infrastructure/storage/compatibility_state.rb +0 -44
- data/lib/stoplight/wiring/light_builder.rb +0 -198
- data/lib/stoplight/wiring/notifier_factory.rb +0 -26
- data/lib/stoplight/wiring/system/light_builder.rb +0 -47
- data/lib/stoplight/wiring/system/light_factory.rb +0 -64
- data/sig/_private/stoplight/domain/light.rbs +0 -25
- data/sig/_private/stoplight/domain/ports/data_store.rbs +0 -76
- data/sig/_private/stoplight/domain/ports/light_factory.rbs +0 -80
- data/sig/_private/stoplight/domain/strategies/green_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/strategies/red_run_strategy.rbs +0 -17
- data/sig/_private/stoplight/domain/tracker/base.rbs +0 -8
- data/sig/_private/stoplight/infrastructure/fail_safe/data_store.rbs +0 -26
- data/sig/_private/stoplight/infrastructure/memory/data_store/metrics.rbs +0 -25
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_store.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/memory/data_store/recovery_lock_token.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store/sliding_window.rbs +0 -27
- data/sig/_private/stoplight/infrastructure/memory/data_store/state.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/memory/data_store.rbs +0 -30
- data/sig/_private/stoplight/infrastructure/notifier/fail_safe.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_store.rbs +0 -24
- data/sig/_private/stoplight/infrastructure/redis/data_store/recovery_lock_token.rbs +0 -21
- data/sig/_private/stoplight/infrastructure/redis/data_store/scripting.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/redis/data_store.rbs +0 -67
- data/sig/_private/stoplight/infrastructure/redis/storage/key_space.rbs +0 -19
- data/sig/_private/stoplight/infrastructure/redis/storage/scripting.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/redis/storage/window_metrics.rbs +0 -34
- data/sig/_private/stoplight/infrastructure/storage/compatibility_metrics.rbs +0 -17
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_lock.rbs +0 -13
- data/sig/_private/stoplight/infrastructure/storage/compatibility_recovery_metrics.rbs +0 -14
- data/sig/_private/stoplight/infrastructure/storage/compatibility_state.rbs +0 -14
- data/sig/_private/stoplight/system/light_builder.rbs +0 -23
- data/sig/_private/stoplight/system/light_factory.rbs +0 -17
- data/sig/_private/stoplight/types.rbs +0 -6
- data/sig/_private/stoplight/wiring/configuration_dsl.rbs +0 -43
- data/sig/_private/stoplight/wiring/default.rbs +0 -26
- data/sig/_private/stoplight/wiring/default_configuration.rbs +0 -29
- data/sig/_private/stoplight/wiring/light_builder.rbs +0 -48
- data/sig/_private/stoplight/wiring/light_factory.rbs +0 -16
- data/sig/_private/stoplight/wiring/notifier_factory.rbs +0 -10
- data/sig/_private/stoplight/wiring/system.rbs +0 -15
- data/sig/_private/stoplight.rbs +0 -48
- data/sig/stoplight/data_store.rbs +0 -19
- data/sig/stoplight/ports/configuration.rbs +0 -19
- data/sig/stoplight/ports/exception_matcher.rbs +0 -8
- data/sig/stoplight/ports/light.rbs +0 -12
- data/sig/stoplight/ports/light_info.rbs +0 -5
- data/sig/stoplight/ports/state_transition_notifier.rbs +0 -15
- /data/sig/{_private/stoplight → stoplight}/common/deprecations.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/compatibility_result.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/metrics_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/ports/state_store.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/state_snapshot.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/domain/traffic_recovery.rbs +0 -0
- /data/sig/{_private → stoplight}/generators/stoplight/install/install_generator.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/fail_safe/storage/recovery_lock_token.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_lock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/memory/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/redis/storage/recovery_metrics.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/infrastructure/system_clock.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/data_store_backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/default_config.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/memory/backend.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set.rbs +0 -0
- /data/sig/{_private/stoplight → stoplight}/wiring/storage_set_builder.rbs +0 -0
data/lib/stoplight/data_store.rb
CHANGED
|
@@ -5,21 +5,31 @@ module Stoplight
|
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
class Redis < Base
|
|
8
|
+
KEY_SPACE = Infrastructure::Redis::Key.new("stoplight", "v6")
|
|
9
|
+
private_constant :KEY_SPACE
|
|
10
|
+
|
|
8
11
|
# @!attribute redis
|
|
9
12
|
# @return [::Redis, ConnectionPool<::Redis>]
|
|
10
13
|
attr_reader :redis
|
|
11
14
|
|
|
12
|
-
# @!attribute warn_on_clock_skew
|
|
13
|
-
# @return [Boolean]
|
|
14
|
-
attr_reader :warn_on_clock_skew
|
|
15
|
-
|
|
16
15
|
# @param redis [::Redis, ConnectionPool<::Redis>]
|
|
17
|
-
|
|
18
|
-
# the application server
|
|
19
|
-
def initialize(redis, warn_on_clock_skew: true)
|
|
20
|
-
@warn_on_clock_skew = warn_on_clock_skew
|
|
16
|
+
def initialize(redis)
|
|
21
17
|
@redis = redis
|
|
22
18
|
end
|
|
19
|
+
|
|
20
|
+
# Immutable key namespace for a light within a system.
|
|
21
|
+
#
|
|
22
|
+
# Produces keys following entity-first structure:
|
|
23
|
+
# stoplight:{version}:{system_id}:{{light_id}}:locks:recovery
|
|
24
|
+
# stoplight:{version}:{system_id}:{{light_id}}:metrics:successes
|
|
25
|
+
# stoplight:{version}:{system_id}:{{light_id}}:state
|
|
26
|
+
#
|
|
27
|
+
# Identifiers are derived from SHA-256 and truncated to 12 characters. Collisions are extremely
|
|
28
|
+
# unlikely at expected system scale.
|
|
29
|
+
#
|
|
30
|
+
# @see Stoplight::Infrastructure::Redis::Key
|
|
31
|
+
def self.key_space = KEY_SPACE
|
|
32
|
+
def key_space = self.class.key_space
|
|
23
33
|
end
|
|
24
34
|
|
|
25
35
|
class Memory < Base
|
|
@@ -6,6 +6,7 @@ module Stoplight
|
|
|
6
6
|
#
|
|
7
7
|
# @api private
|
|
8
8
|
Config = Data.define(
|
|
9
|
+
:id,
|
|
9
10
|
:name,
|
|
10
11
|
:cool_off_time,
|
|
11
12
|
:threshold,
|
|
@@ -21,10 +22,11 @@ module Stoplight
|
|
|
21
22
|
)
|
|
22
23
|
class Config
|
|
23
24
|
def cool_off_time_in_milliseconds
|
|
24
|
-
|
|
25
|
+
cool_off_time * 1_000
|
|
25
26
|
end
|
|
26
27
|
|
|
27
28
|
def with(
|
|
29
|
+
id: T.undefined,
|
|
28
30
|
name: T.undefined,
|
|
29
31
|
cool_off_time: T.undefined,
|
|
30
32
|
threshold: T.undefined,
|
|
@@ -39,6 +41,7 @@ module Stoplight
|
|
|
39
41
|
data_store: T.undefined
|
|
40
42
|
)
|
|
41
43
|
super(
|
|
44
|
+
id: id.is_a?(Undefined) ? self.id : id,
|
|
42
45
|
name: name.is_a?(Undefined) ? self.name : name,
|
|
43
46
|
cool_off_time: cool_off_time.is_a?(Undefined) ? self.cool_off_time : cool_off_time,
|
|
44
47
|
threshold: threshold.is_a?(Undefined) ? self.threshold : threshold,
|
|
@@ -9,6 +9,15 @@ module Stoplight
|
|
|
9
9
|
@skipped = skipped
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
+
def with(tracked: T.undefined, skipped: T.undefined)
|
|
13
|
+
return self if tracked.is_a?(Undefined) && skipped.is_a?(Undefined)
|
|
14
|
+
|
|
15
|
+
self.class.new(
|
|
16
|
+
tracked: tracked.is_a?(Undefined) ? @tracked : Array(tracked),
|
|
17
|
+
skipped: skipped.is_a?(Undefined) ? @skipped : Array(skipped)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
12
21
|
def track?(error)
|
|
13
22
|
!skipped?(error) && tracked?(error)
|
|
14
23
|
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Stoplight
|
|
6
|
+
module Domain
|
|
7
|
+
# Helper to generate light/systems ids
|
|
8
|
+
module Id
|
|
9
|
+
# Derives id from a string
|
|
10
|
+
def self.for(name) = T.must(Digest::SHA256.hexdigest(name)[0, 12])
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -5,24 +5,34 @@ module Stoplight
|
|
|
5
5
|
#
|
|
6
6
|
# @api private use +Stoplight()+ method instead
|
|
7
7
|
class Light
|
|
8
|
-
|
|
9
|
-
include ConfigurationBuilderInterface # steep:ignore
|
|
10
|
-
|
|
8
|
+
# @api public
|
|
11
9
|
attr_reader :name
|
|
12
|
-
|
|
10
|
+
# @api private
|
|
13
11
|
attr_reader :green_run_strategy
|
|
12
|
+
# @api private
|
|
14
13
|
attr_reader :yellow_run_strategy
|
|
14
|
+
# @api private
|
|
15
15
|
attr_reader :red_run_strategy
|
|
16
|
-
|
|
16
|
+
# @api private
|
|
17
17
|
attr_reader :state_store
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# @api private
|
|
20
|
+
def initialize(
|
|
21
|
+
name,
|
|
22
|
+
green_run_strategy:,
|
|
23
|
+
yellow_run_strategy:,
|
|
24
|
+
red_run_strategy:,
|
|
25
|
+
state_store:,
|
|
26
|
+
lock_control:,
|
|
27
|
+
error_tracking_policy:
|
|
28
|
+
)
|
|
20
29
|
@name = name
|
|
21
30
|
@green_run_strategy = green_run_strategy
|
|
22
31
|
@yellow_run_strategy = yellow_run_strategy
|
|
23
32
|
@red_run_strategy = red_run_strategy
|
|
24
|
-
@factory = factory
|
|
25
33
|
@state_store = state_store
|
|
34
|
+
@lock_control = lock_control
|
|
35
|
+
@error_tracking_policy = error_tracking_policy
|
|
26
36
|
end
|
|
27
37
|
|
|
28
38
|
# Returns the current state of the light:
|
|
@@ -30,6 +40,7 @@ module Stoplight
|
|
|
30
40
|
# * +Stoplight::State::LOCKED_RED+ -- light is locked red and blocks all traffic
|
|
31
41
|
# * +Stoplight::State::UNLOCKED+ -- light is not locked and follow the configured rules
|
|
32
42
|
#
|
|
43
|
+
# @api public
|
|
33
44
|
def state = state_snapshot.locked_state
|
|
34
45
|
|
|
35
46
|
# Returns current color:
|
|
@@ -41,6 +52,7 @@ module Stoplight
|
|
|
41
52
|
# light = Stoplight('example')
|
|
42
53
|
# light.color #=> Color::GREEN
|
|
43
54
|
#
|
|
55
|
+
# @api public
|
|
44
56
|
def color = state_snapshot.color
|
|
45
57
|
|
|
46
58
|
# Runs the given block of code with this circuit breaker
|
|
@@ -53,14 +65,22 @@ module Stoplight
|
|
|
53
65
|
# light = Stoplight('example')
|
|
54
66
|
# light.run(->(error) { 0 }) { 1 / 0 } #=> 0
|
|
55
67
|
#
|
|
68
|
+
# @example Overriding tracked errors for one run
|
|
69
|
+
# light.run(tracked_errors: [Timeout::Error]) { fetch_data }
|
|
70
|
+
#
|
|
56
71
|
# @param fallback fallback code to run if the circuit breaker is open
|
|
72
|
+
# @param tracked_errors errors to track for this run; replaces the configured list
|
|
73
|
+
# @param skipped_errors errors to skip for this run; replaces the configured list
|
|
57
74
|
# @raise [Stoplight::Error::RedLight]
|
|
58
|
-
|
|
75
|
+
#
|
|
76
|
+
# @api public
|
|
77
|
+
def run(fallback = nil, tracked_errors: T.undefined, skipped_errors: T.undefined, &code)
|
|
59
78
|
raise ArgumentError, "nothing to run. Please, pass a block into `Light#run`" unless block_given?
|
|
60
79
|
|
|
61
80
|
state_snapshot.then do |state_snapshot|
|
|
62
81
|
strategy = state_strategy_factory(state_snapshot.color)
|
|
63
|
-
|
|
82
|
+
error_tracking_policy = @error_tracking_policy.with(tracked: tracked_errors, skipped: skipped_errors)
|
|
83
|
+
strategy.execute(fallback, state_snapshot:, error_tracking_policy:, &code)
|
|
64
84
|
end
|
|
65
85
|
end
|
|
66
86
|
|
|
@@ -72,14 +92,10 @@ module Stoplight
|
|
|
72
92
|
#
|
|
73
93
|
# @param color should be either +Color::RED+ or +Color::GREEN+
|
|
74
94
|
# @return locked light
|
|
95
|
+
#
|
|
96
|
+
# @api public
|
|
75
97
|
def lock(color)
|
|
76
|
-
|
|
77
|
-
when Color::RED then State::LOCKED_RED
|
|
78
|
-
when Color::GREEN then State::LOCKED_GREEN
|
|
79
|
-
else raise Error::IncorrectColor
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
state_store.set_state(state)
|
|
98
|
+
@lock_control.lock(color)
|
|
83
99
|
|
|
84
100
|
self
|
|
85
101
|
end
|
|
@@ -92,72 +108,14 @@ module Stoplight
|
|
|
92
108
|
# light.unlock
|
|
93
109
|
#
|
|
94
110
|
# @return returns unlocked light (circuit breaker)
|
|
111
|
+
#
|
|
112
|
+
# @api public
|
|
95
113
|
def unlock
|
|
96
|
-
|
|
114
|
+
@lock_control.unlock
|
|
97
115
|
|
|
98
116
|
self
|
|
99
117
|
end
|
|
100
118
|
|
|
101
|
-
# Two lights considered equal if they have the same configuration.
|
|
102
|
-
def ==(other)
|
|
103
|
-
other.is_a?(self.class) && factory == other.factory
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Reconfigures the light with updated settings and returns a new instance.
|
|
107
|
-
#
|
|
108
|
-
# This method allows you to modify the configuration of a +Stoplight::Light+ object
|
|
109
|
-
# by providing a hash of settings. The original light remains unchanged, and a new
|
|
110
|
-
# light instance with the updated configuration is returned.
|
|
111
|
-
#
|
|
112
|
-
# @param settings [Hash] A hash of configuration options to update.
|
|
113
|
-
# @option settings [String] :name The name of the light.
|
|
114
|
-
# @option settings [Numeric] :cool_off_time The cool-off time in seconds before the light attempts recovery.
|
|
115
|
-
# @option settings [Numeric] :threshold The failure threshold to trigger the red state.
|
|
116
|
-
# @option settings [Numeric] :window_size The time window in seconds for counting failures.
|
|
117
|
-
# @option settings [Stoplight::DataStore::Base] :data_store The data store to use for persisting light state.
|
|
118
|
-
# @option settings [Array<Stoplight::Domain::AbstractStateTransitionNotifier>] :notifiers A list of notifiers to handle light events.
|
|
119
|
-
# @option settings [Proc] :error_notifier A custom error notifier to handle exceptions.
|
|
120
|
-
# @option settings [Array<StandardError>] :tracked_errors A list of errors to track for failure counting.
|
|
121
|
-
# @option settings [Array<StandardError>] :skipped_errors A list of errors to skip from failure counting.
|
|
122
|
-
# @return [Stoplight::Light] A new `Stoplight::Light` instance with the updated configuration.
|
|
123
|
-
#
|
|
124
|
-
# @example Reconfiguring a light with custom settings
|
|
125
|
-
# light = Stoplight('payment-api')
|
|
126
|
-
#
|
|
127
|
-
# # Create a light for invoices with a higher threshold
|
|
128
|
-
# invoices_light = light.with(tracked_errors: [TimeoutError], threshold: 10)
|
|
129
|
-
#
|
|
130
|
-
# # Create a light for payments with a lower threshold
|
|
131
|
-
# payment_light = light.with(threshold: 5)
|
|
132
|
-
#
|
|
133
|
-
# # Run the lights with their respective configurations
|
|
134
|
-
# invoices_light.run(->(error) { [] }) { call_invoices_api }
|
|
135
|
-
# payment_light.run(->(error) { nil }) { call_payment_api }
|
|
136
|
-
# @deprecated
|
|
137
|
-
# @see +Stoplight()+
|
|
138
|
-
# steep:ignore:start
|
|
139
|
-
def with(**settings)
|
|
140
|
-
deprecate(<<~MSG)
|
|
141
|
-
Light#with is deprecated and will be removed in v6.0.0.
|
|
142
|
-
|
|
143
|
-
Circuit breakers should be configured once at creation, not cloned with
|
|
144
|
-
modifications.
|
|
145
|
-
|
|
146
|
-
Instead of:
|
|
147
|
-
light = Stoplight('api-call', threshold: 5)
|
|
148
|
-
modified = light.with(threshold: 10)
|
|
149
|
-
|
|
150
|
-
Configure correctly from the start:
|
|
151
|
-
Stoplight('api-call', threshold: 10)
|
|
152
|
-
MSG
|
|
153
|
-
with_without_warning(**settings)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
private def with_without_warning(**settings)
|
|
157
|
-
factory.build_with(**settings)
|
|
158
|
-
end
|
|
159
|
-
# steep:ignore:end
|
|
160
|
-
|
|
161
119
|
private
|
|
162
120
|
|
|
163
121
|
def state_strategy_factory(color)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Domain
|
|
5
|
+
# Pins a light to a color (or releases the pin), writing the lock state and emitting +Telemetry::LockChanged+.
|
|
6
|
+
#
|
|
7
|
+
# @example
|
|
8
|
+
# lock_control = Stoplight::Domain::LockControl.new(state_store:, emitter:)
|
|
9
|
+
# lock_control.lock(Stoplight::Color::RED)
|
|
10
|
+
# lock_control.unlock
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
class LockControl
|
|
14
|
+
def initialize(state_store:, emitter:)
|
|
15
|
+
@state_store = state_store
|
|
16
|
+
@emitter = emitter
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param color - +Color::RED+ or +Color::GREEN+
|
|
20
|
+
# @raise [Stoplight::Error::IncorrectColor] for any other color
|
|
21
|
+
def lock(color)
|
|
22
|
+
state = case color
|
|
23
|
+
when Color::RED then State::LOCKED_RED
|
|
24
|
+
when Color::GREEN then State::LOCKED_GREEN
|
|
25
|
+
else raise Error::IncorrectColor
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
@emitter.emit(Telemetry::LockChanged) do
|
|
29
|
+
state_snapshot = @state_store.state_snapshot
|
|
30
|
+
|
|
31
|
+
Telemetry::LockChanged.new(from_color: state_snapshot.color, to_color: color,
|
|
32
|
+
from_state: state_snapshot.locked_state, to_state: state)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
@state_store.set_state(state)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def unlock
|
|
39
|
+
@emitter.emit(Telemetry::LockChanged) do
|
|
40
|
+
state_snapshot = @state_store.state_snapshot
|
|
41
|
+
from_color = state_snapshot.color
|
|
42
|
+
from_state = state_snapshot.locked_state
|
|
43
|
+
to_state = State::UNLOCKED
|
|
44
|
+
# Reuses StateSnapshot#color instead of re-deriving it: unlocking can reveal a
|
|
45
|
+
# breach/recovery color that isn't RED or GREEN, unlike a lock's fixed color.
|
|
46
|
+
to_color = state_snapshot.with(locked_state: to_state).color
|
|
47
|
+
|
|
48
|
+
Telemetry::LockChanged.new(from_color:, to_color:, from_state:, to_state:)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
@state_store.set_state(State::UNLOCKED)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Domain
|
|
5
|
+
class MatcherValidator
|
|
6
|
+
class << self
|
|
7
|
+
def call(error_matcher)
|
|
8
|
+
name = error_matcher.is_a?(Module) ? error_matcher.name : nil
|
|
9
|
+
raise ArgumentError, message(error_matcher) if name.nil?
|
|
10
|
+
|
|
11
|
+
name
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def message(error_matcher)
|
|
17
|
+
"error matcher #{error_matcher.inspect} must be a named class or module (e.g. StandardError, " \
|
|
18
|
+
"or a custom class/module overriding `===`) - procs, anonymous classes, and instances have no " \
|
|
19
|
+
"stable name and cannot be used here."
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -29,7 +29,7 @@ module Stoplight
|
|
|
29
29
|
Color::GREEN
|
|
30
30
|
elsif locked_state == State::LOCKED_RED
|
|
31
31
|
Color::RED
|
|
32
|
-
elsif (recovery_scheduled_after && recovery_scheduled_after! < time) ||
|
|
32
|
+
elsif (recovery_scheduled_after && recovery_scheduled_after! < time) || recovery_started?
|
|
33
33
|
Color::YELLOW
|
|
34
34
|
elsif breached_at
|
|
35
35
|
Color::RED
|
|
@@ -44,11 +44,7 @@ module Stoplight
|
|
|
44
44
|
# This method indicates whether the recovery has already started explicitly
|
|
45
45
|
#
|
|
46
46
|
def recovery_started?
|
|
47
|
-
|
|
48
|
-
false
|
|
49
|
-
else
|
|
50
|
-
recovery_started_at! <= time
|
|
51
|
-
end
|
|
47
|
+
!!recovery_started_at
|
|
52
48
|
end
|
|
53
49
|
|
|
54
50
|
def recovery_started_at!
|
|
@@ -10,9 +10,10 @@ module Stoplight
|
|
|
10
10
|
#
|
|
11
11
|
# @api private
|
|
12
12
|
class GreenRunStrategy
|
|
13
|
-
def initialize(
|
|
14
|
-
@error_tracking_policy = error_tracking_policy
|
|
13
|
+
def initialize(request_tracker:, run_recorder:, clock:)
|
|
15
14
|
@request_tracker = request_tracker
|
|
15
|
+
@run_recorder = run_recorder
|
|
16
|
+
@clock = clock
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
# Executes the provided code block when the light is in the green state.
|
|
@@ -22,37 +23,50 @@ module Stoplight
|
|
|
22
23
|
# @yield The code block to execute.
|
|
23
24
|
# @return The result of the code block if successful.
|
|
24
25
|
# @raise re-raises the error if it is not tracked or no fallback is provided.
|
|
25
|
-
def execute(fallback, state_snapshot:, &code)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
def execute(fallback, state_snapshot:, error_tracking_policy:, &code)
|
|
27
|
+
started_at = capture_started_at
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
result = code.call
|
|
31
|
+
rescue => error
|
|
32
|
+
if error_tracking_policy.track?(error)
|
|
33
|
+
record_error(error, duration_ms: duration_since(started_at), fallback_used: !fallback.nil?)
|
|
34
|
+
|
|
35
|
+
if fallback
|
|
36
|
+
fallback.call(error)
|
|
37
|
+
else
|
|
38
|
+
raise
|
|
39
|
+
end
|
|
36
40
|
else
|
|
41
|
+
# User chose to not track the error, so we record it as a success
|
|
42
|
+
record_success(duration_ms: duration_since(started_at), error: error)
|
|
37
43
|
raise
|
|
38
44
|
end
|
|
39
45
|
else
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
raise
|
|
46
|
+
record_success(duration_ms: duration_since(started_at))
|
|
47
|
+
result
|
|
43
48
|
end
|
|
44
49
|
end
|
|
45
50
|
|
|
46
51
|
private
|
|
47
52
|
|
|
48
|
-
attr_reader :config
|
|
49
53
|
attr_reader :request_tracker
|
|
50
54
|
|
|
51
|
-
def
|
|
55
|
+
def capture_started_at
|
|
56
|
+
@clock.monotonic_millis if @run_recorder.subscribed?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def duration_since(started_at)
|
|
60
|
+
@clock.monotonic_millis - started_at if started_at
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def record_error(error, duration_ms:, fallback_used:)
|
|
64
|
+
@run_recorder.record_failure(error, duration_ms:, fallback_used:)
|
|
52
65
|
request_tracker.record_failure(error)
|
|
53
66
|
end
|
|
54
67
|
|
|
55
|
-
def record_success
|
|
68
|
+
def record_success(duration_ms:, error: nil)
|
|
69
|
+
@run_recorder.record_success(duration_ms:, error:)
|
|
56
70
|
request_tracker.record_success
|
|
57
71
|
end
|
|
58
72
|
end
|
|
@@ -10,9 +10,10 @@ module Stoplight
|
|
|
10
10
|
#
|
|
11
11
|
# @api private
|
|
12
12
|
class RedRunStrategy
|
|
13
|
-
def initialize(name:, cool_off_time:)
|
|
13
|
+
def initialize(name:, cool_off_time:, run_recorder:)
|
|
14
14
|
@name = name
|
|
15
15
|
@cool_off_time = cool_off_time
|
|
16
|
+
@run_recorder = run_recorder
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
# Executes the fallback proc when the light is in the red state.
|
|
@@ -21,7 +22,12 @@ module Stoplight
|
|
|
21
22
|
# @param state_snapshot
|
|
22
23
|
# @return The result of the fallback proc if provided.
|
|
23
24
|
# @raise [Stoplight::Error::RedLight] Raises an error if no fallback is provided.
|
|
24
|
-
def execute(fallback, state_snapshot:)
|
|
25
|
+
def execute(fallback, state_snapshot:, error_tracking_policy:)
|
|
26
|
+
@run_recorder.record_blocked(
|
|
27
|
+
fallback_used: !fallback.nil?,
|
|
28
|
+
retry_after: state_snapshot.recovery_scheduled_after
|
|
29
|
+
)
|
|
30
|
+
|
|
25
31
|
if fallback
|
|
26
32
|
fallback.call(nil)
|
|
27
33
|
else
|
|
@@ -13,24 +13,24 @@ module Stoplight
|
|
|
13
13
|
class YellowRunStrategy
|
|
14
14
|
def initialize(
|
|
15
15
|
name:,
|
|
16
|
-
error_tracking_policy:,
|
|
17
|
-
notifiers:,
|
|
18
16
|
request_tracker:,
|
|
19
|
-
red_run_strategy:,
|
|
20
17
|
state_store:,
|
|
21
18
|
metrics_store:,
|
|
22
19
|
recovery_lock_store:,
|
|
23
|
-
config
|
|
20
|
+
config:,
|
|
21
|
+
clock:,
|
|
22
|
+
run_recorder:,
|
|
23
|
+
emitter:
|
|
24
24
|
)
|
|
25
|
-
@notifiers = notifiers
|
|
26
25
|
@request_tracker = request_tracker
|
|
27
|
-
@red_run_strategy = red_run_strategy
|
|
28
26
|
@state_store = state_store
|
|
29
27
|
@metrics_store = metrics_store
|
|
30
28
|
@recovery_lock_store = recovery_lock_store
|
|
31
29
|
@name = name
|
|
32
|
-
@error_tracking_policy = error_tracking_policy
|
|
33
30
|
@config = config
|
|
31
|
+
@clock = clock
|
|
32
|
+
@run_recorder = run_recorder
|
|
33
|
+
@emitter = emitter
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Executes the provided code block when the light is in the yellow state.
|
|
@@ -40,62 +40,84 @@ module Stoplight
|
|
|
40
40
|
# @yield The code block to execute.
|
|
41
41
|
# @return The result of the code block if successful.
|
|
42
42
|
# @raise Re-raises the error if it is not tracked or no fallback is provided.
|
|
43
|
-
def execute(fallback, state_snapshot:, &code)
|
|
43
|
+
def execute(fallback, state_snapshot:, error_tracking_policy:, &code)
|
|
44
44
|
# Everything withing this block executed exclusively:
|
|
45
45
|
# - enter recovery
|
|
46
46
|
# - execute user's code
|
|
47
47
|
# - record outcome
|
|
48
48
|
# - transition to green or red if needed
|
|
49
|
-
with_recovery_lock(fallback:, state_snapshot
|
|
49
|
+
with_recovery_lock(fallback:, state_snapshot:) do |started_at|
|
|
50
50
|
enter_recovery(state_snapshot)
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
record_recovery_probe_failure(error)
|
|
52
|
+
begin
|
|
53
|
+
result = code.call
|
|
54
|
+
rescue => error
|
|
55
|
+
if error_tracking_policy.track?(error)
|
|
56
|
+
record_recovery_probe_failure(error, duration_ms: duration_since(started_at), fallback_used: !fallback.nil?)
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
if fallback
|
|
59
|
+
fallback.call(error)
|
|
60
|
+
else
|
|
61
|
+
raise
|
|
62
|
+
end
|
|
61
63
|
else
|
|
64
|
+
record_recovery_probe_success(duration_ms: duration_since(started_at), error:)
|
|
62
65
|
raise
|
|
63
66
|
end
|
|
64
67
|
else
|
|
65
|
-
record_recovery_probe_success
|
|
66
|
-
|
|
68
|
+
record_recovery_probe_success(duration_ms: duration_since(started_at))
|
|
69
|
+
result
|
|
67
70
|
end
|
|
68
71
|
end
|
|
69
72
|
end
|
|
70
73
|
|
|
71
74
|
private
|
|
72
75
|
|
|
73
|
-
attr_reader :notifiers
|
|
74
76
|
attr_reader :request_tracker
|
|
75
|
-
attr_reader :red_run_strategy
|
|
76
77
|
attr_reader :state_store
|
|
77
78
|
attr_reader :metrics_store
|
|
78
79
|
attr_reader :recovery_lock_store
|
|
79
80
|
|
|
80
|
-
def with_recovery_lock(fallback:, state_snapshot
|
|
81
|
+
def with_recovery_lock(fallback:, state_snapshot:)
|
|
81
82
|
recovery_lock_token = recovery_lock_store.acquire_lock
|
|
82
83
|
if recovery_lock_token.nil?
|
|
83
|
-
|
|
84
|
+
@run_recorder.record_blocked(
|
|
85
|
+
fallback_used: !fallback.nil?,
|
|
86
|
+
retry_after: state_snapshot.recovery_scheduled_after
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return fallback.call(nil) if fallback
|
|
90
|
+
|
|
91
|
+
raise Error::RedLight.new(
|
|
92
|
+
@name,
|
|
93
|
+
cool_off_time: @config.cool_off_time,
|
|
94
|
+
retry_after: state_snapshot.recovery_scheduled_after
|
|
95
|
+
)
|
|
84
96
|
end
|
|
85
97
|
|
|
86
98
|
begin
|
|
87
|
-
yield
|
|
99
|
+
yield capture_started_at
|
|
88
100
|
ensure
|
|
89
101
|
recovery_lock_store.release_lock(recovery_lock_token)
|
|
90
102
|
end
|
|
91
103
|
end
|
|
92
104
|
|
|
93
|
-
def
|
|
94
|
-
|
|
105
|
+
def capture_started_at
|
|
106
|
+
@clock.monotonic_millis
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def duration_since(started_at)
|
|
110
|
+
@clock.monotonic_millis - started_at
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def record_recovery_probe_success(duration_ms:, error: nil)
|
|
114
|
+
@run_recorder.record_success(duration_ms:, error:)
|
|
115
|
+
request_tracker.record_success(duration_ms:)
|
|
95
116
|
end
|
|
96
117
|
|
|
97
|
-
def record_recovery_probe_failure(error)
|
|
98
|
-
|
|
118
|
+
def record_recovery_probe_failure(error, duration_ms:, fallback_used:)
|
|
119
|
+
@run_recorder.record_failure(error, duration_ms:, fallback_used:)
|
|
120
|
+
request_tracker.record_failure(error, duration_ms:)
|
|
99
121
|
end
|
|
100
122
|
|
|
101
123
|
def enter_recovery(state_snapshot)
|
|
@@ -103,10 +125,12 @@ module Stoplight
|
|
|
103
125
|
|
|
104
126
|
state_store.transition_to_color(Color::YELLOW)
|
|
105
127
|
metrics_store.clear
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
128
|
+
@emitter.emit(Telemetry::RecoveryStarted) do
|
|
129
|
+
Telemetry::RecoveryStarted.new(
|
|
130
|
+
from_color: Color::RED,
|
|
131
|
+
to_color: Color::YELLOW,
|
|
132
|
+
breached_at: T.must(state_snapshot.breached_at)
|
|
133
|
+
)
|
|
110
134
|
end
|
|
111
135
|
end
|
|
112
136
|
end
|