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
|
@@ -10,22 +10,22 @@ module Stoplight
|
|
|
10
10
|
# config = Stoplight::Domain::Config.new(threshold: 0.6, window_size: 300, traffic_control:)
|
|
11
11
|
#
|
|
12
12
|
# Will switch to red if 60% error rate reached within the 5-minute (300 seconds) sliding window.
|
|
13
|
-
# By default this traffic control strategy starts evaluating only after 10 requests have been made. You can
|
|
14
|
-
# adjust this by passing a different value for `min_requests` when initializing the strategy.
|
|
15
|
-
#
|
|
16
|
-
# traffic_control = Stoplight::Domain::TrafficControl::ErrorRate.new(min_requests: 100)
|
|
17
13
|
#
|
|
18
14
|
# @api private
|
|
19
15
|
class ErrorRate
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
NAME = :error_rate
|
|
17
|
+
|
|
18
|
+
MIN_REQUESTS = 100
|
|
19
|
+
private_constant :MIN_REQUESTS
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
def check_compatibility(config)
|
|
27
25
|
if config.window_size.nil?
|
|
28
26
|
CompatibilityResult.incompatible("`window_size` should be set")
|
|
27
|
+
elsif !config.threshold.is_a?(Numeric)
|
|
28
|
+
CompatibilityResult.incompatible("`threshold` should be a number")
|
|
29
29
|
elsif config.threshold < 0 || config.threshold > 1
|
|
30
30
|
CompatibilityResult.incompatible("`threshold` should be between 0 and 1")
|
|
31
31
|
else
|
|
@@ -39,16 +39,20 @@ module Stoplight
|
|
|
39
39
|
|
|
40
40
|
raise ArgumentError, "accepts only windowed metrics" if error_rate.nil? || requests.nil?
|
|
41
41
|
|
|
42
|
-
requests >=
|
|
42
|
+
requests >= MIN_REQUESTS && error_rate >= config.threshold
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
def name = NAME.to_s
|
|
46
|
+
|
|
45
47
|
def ==(other)
|
|
46
|
-
other.is_a?(self.class)
|
|
48
|
+
other.is_a?(self.class)
|
|
47
49
|
end
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
def eql?(other)
|
|
52
|
+
self == other
|
|
53
|
+
end
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
def hash = self.class.hash
|
|
52
56
|
end
|
|
53
57
|
end
|
|
54
58
|
end
|
|
@@ -34,11 +34,13 @@ module Stoplight
|
|
|
34
34
|
#
|
|
35
35
|
# @api private
|
|
36
36
|
class ConsecutiveSuccesses
|
|
37
|
+
NAME = :consecutive_successes
|
|
38
|
+
|
|
37
39
|
def check_compatibility(config)
|
|
38
|
-
if config.recovery_threshold
|
|
39
|
-
CompatibilityResult.incompatible("`recovery_threshold` should be bigger than 0")
|
|
40
|
-
elsif !config.recovery_threshold.is_a?(Integer)
|
|
40
|
+
if !config.recovery_threshold.is_a?(Integer)
|
|
41
41
|
CompatibilityResult.incompatible("`recovery_threshold` should be an integer")
|
|
42
|
+
elsif config.recovery_threshold <= 0
|
|
43
|
+
CompatibilityResult.incompatible("`recovery_threshold` should be bigger than 0")
|
|
42
44
|
else
|
|
43
45
|
CompatibilityResult.compatible
|
|
44
46
|
end
|
|
@@ -55,7 +57,11 @@ module Stoplight
|
|
|
55
57
|
end
|
|
56
58
|
end
|
|
57
59
|
|
|
60
|
+
def hash = self.class.hash
|
|
61
|
+
|
|
58
62
|
def ==(other) = other.is_a?(self.class)
|
|
63
|
+
|
|
64
|
+
def name = NAME.to_s
|
|
59
65
|
end
|
|
60
66
|
end
|
|
61
67
|
end
|
data/lib/stoplight/error.rb
CHANGED
|
@@ -5,13 +5,22 @@ module Stoplight
|
|
|
5
5
|
class Base < StandardError
|
|
6
6
|
end
|
|
7
7
|
|
|
8
|
+
class UnregisteredLightError < Base
|
|
9
|
+
end
|
|
10
|
+
|
|
8
11
|
class ConfigurationError < Base
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
class IncorrectColor < Base
|
|
12
15
|
end
|
|
13
16
|
|
|
17
|
+
class TooManySubscriptions < Base
|
|
18
|
+
end
|
|
19
|
+
|
|
14
20
|
class RedLight < Base
|
|
21
|
+
EXCEPTION_MESSAGE = 'Stoplight "%{light_name}" is red - traffic stopped until recovery.'
|
|
22
|
+
private_constant :EXCEPTION_MESSAGE
|
|
23
|
+
|
|
15
24
|
# @!attribute light_name
|
|
16
25
|
# @return [String] The light's name
|
|
17
26
|
attr_reader :light_name
|
|
@@ -39,7 +48,7 @@ module Stoplight
|
|
|
39
48
|
@cool_off_time = cool_off_time
|
|
40
49
|
@retry_after = retry_after
|
|
41
50
|
|
|
42
|
-
super(light_name)
|
|
51
|
+
super(format(EXCEPTION_MESSAGE, light_name: light_name))
|
|
43
52
|
end
|
|
44
53
|
end
|
|
45
54
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
class ConfigSerializer
|
|
6
|
+
class << self
|
|
7
|
+
def call(config)
|
|
8
|
+
{
|
|
9
|
+
"id" => config.id,
|
|
10
|
+
"name" => config.name,
|
|
11
|
+
"cool_off_time" => config.cool_off_time,
|
|
12
|
+
"threshold" => config.threshold,
|
|
13
|
+
"recovery_threshold" => config.recovery_threshold,
|
|
14
|
+
"window_size" => config.window_size,
|
|
15
|
+
"tracked_errors" => config.tracked_errors.map(&:name),
|
|
16
|
+
"skipped_errors" => config.skipped_errors.map(&:name),
|
|
17
|
+
"traffic_control" => {
|
|
18
|
+
"strategy" => serialize_traffic_control(config.traffic_control)
|
|
19
|
+
},
|
|
20
|
+
"traffic_recovery" => {
|
|
21
|
+
"strategy" => serialize_traffic_recovery(config.traffic_recovery)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def serialize_traffic_control(traffic_control)
|
|
29
|
+
case traffic_control
|
|
30
|
+
when Domain::TrafficControl::ConsecutiveErrors
|
|
31
|
+
Domain::TrafficControl::ConsecutiveErrors::NAME.to_s
|
|
32
|
+
when Domain::TrafficControl::ErrorRate
|
|
33
|
+
Domain::TrafficControl::ErrorRate::NAME.to_s
|
|
34
|
+
else
|
|
35
|
+
raise TypeError, "unable to serialize traffic_control: #{traffic_control}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def serialize_traffic_recovery(traffic_recovery)
|
|
40
|
+
case traffic_recovery
|
|
41
|
+
when Domain::TrafficRecovery::ConsecutiveSuccesses
|
|
42
|
+
Domain::TrafficRecovery::ConsecutiveSuccesses::NAME.to_s
|
|
43
|
+
else
|
|
44
|
+
raise TypeError, "unable to serialize traffic_recovery: #{traffic_recovery}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module FailSafe
|
|
6
|
+
# Wraps a user-provided error notifier so a failure in the notifier itself
|
|
7
|
+
# cannot escape to callers of fail-safe components.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
class ErrorNotifier
|
|
11
|
+
attr_reader :error_notifier
|
|
12
|
+
|
|
13
|
+
def initialize(error_notifier:)
|
|
14
|
+
@error_notifier = error_notifier
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call(error)
|
|
18
|
+
error_notifier.call(error)
|
|
19
|
+
rescue => notifier_error
|
|
20
|
+
warn "Stoplight error_notifier raised: #{notifier_error.message}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module FailSafe
|
|
6
|
+
module Storage
|
|
7
|
+
class Registry
|
|
8
|
+
def initialize(primary_registry:, error_notifier:, failover_registry:, circuit_breaker:)
|
|
9
|
+
@primary_registry = primary_registry
|
|
10
|
+
@error_notifier = error_notifier
|
|
11
|
+
@failover_registry = failover_registry
|
|
12
|
+
@circuit_breaker = circuit_breaker
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def ids
|
|
16
|
+
fallback = ->(error) {
|
|
17
|
+
@error_notifier.call(error) if error
|
|
18
|
+
@failover_registry.ids
|
|
19
|
+
}
|
|
20
|
+
@circuit_breaker.run(fallback) do
|
|
21
|
+
@primary_registry.ids
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def all_configs
|
|
26
|
+
fallback = ->(error) {
|
|
27
|
+
@error_notifier.call(error) if error
|
|
28
|
+
@failover_registry.all_configs
|
|
29
|
+
}
|
|
30
|
+
@circuit_breaker.run(fallback) do
|
|
31
|
+
@primary_registry.all_configs
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def register(config)
|
|
36
|
+
fallback = ->(error) {
|
|
37
|
+
@error_notifier.call(error) if error
|
|
38
|
+
@failover_registry.register(config)
|
|
39
|
+
}
|
|
40
|
+
@circuit_breaker.run(fallback) do
|
|
41
|
+
@primary_registry.register(config)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def unregister(id)
|
|
46
|
+
fallback = ->(error) {
|
|
47
|
+
@error_notifier.call(error) if error
|
|
48
|
+
@failover_registry.unregister(id)
|
|
49
|
+
}
|
|
50
|
+
@circuit_breaker.run(fallback) do
|
|
51
|
+
@primary_registry.unregister(id)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def config_for(id)
|
|
56
|
+
fallback = ->(error) {
|
|
57
|
+
@error_notifier.call(error) if error
|
|
58
|
+
@failover_registry.config_for(id)
|
|
59
|
+
}
|
|
60
|
+
@circuit_breaker.run(fallback) do
|
|
61
|
+
@primary_registry.config_for(id)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Memory
|
|
6
|
+
module Storage
|
|
7
|
+
class Registry
|
|
8
|
+
def ids = []
|
|
9
|
+
def all_configs = []
|
|
10
|
+
|
|
11
|
+
def register(config) = nil
|
|
12
|
+
def unregister(id) = nil
|
|
13
|
+
def config_for(id) = nil
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -28,64 +28,50 @@ module Stoplight
|
|
|
28
28
|
|
|
29
29
|
# Get metrics for the current light
|
|
30
30
|
def metrics_snapshot
|
|
31
|
-
mutex.synchronize
|
|
32
|
-
Domain::MetricsSnapshot.new(
|
|
33
|
-
errors: nil,
|
|
34
|
-
successes: nil,
|
|
35
|
-
consecutive_errors: consecutive_errors.to_i,
|
|
36
|
-
consecutive_successes: consecutive_successes.to_i,
|
|
37
|
-
last_error: last_error,
|
|
38
|
-
last_success_at: last_success_at
|
|
39
|
-
)
|
|
40
|
-
end
|
|
31
|
+
@mutex.synchronize { build_metrics_snapshot }
|
|
41
32
|
end
|
|
42
33
|
|
|
43
34
|
# Records successful circuit breaker execution
|
|
44
35
|
def record_success
|
|
45
|
-
current_time = clock.current_time
|
|
36
|
+
current_time = @clock.current_time
|
|
46
37
|
|
|
47
|
-
mutex.synchronize do
|
|
48
|
-
if last_success_at.nil? || current_time > last_success_at
|
|
49
|
-
|
|
38
|
+
@mutex.synchronize do
|
|
39
|
+
if @last_success_at.nil? || current_time > T.must(@last_success_at)
|
|
40
|
+
@last_success_at = current_time
|
|
50
41
|
end
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
43
|
+
@consecutive_errors = 0
|
|
44
|
+
@consecutive_successes += 1
|
|
54
45
|
end
|
|
55
46
|
end
|
|
56
47
|
|
|
57
48
|
# Records failed circuit breaker execution
|
|
58
49
|
def record_failure(exception)
|
|
59
|
-
current_time = clock.current_time
|
|
50
|
+
current_time = @clock.current_time
|
|
60
51
|
failure = Domain::Failure.from_error(exception, time: current_time)
|
|
61
|
-
last_error_at = self.last_error_at
|
|
62
52
|
|
|
63
|
-
mutex.synchronize do
|
|
53
|
+
@mutex.synchronize do
|
|
54
|
+
last_error_at = self.last_error_at
|
|
55
|
+
|
|
64
56
|
if last_error_at.nil? || failure.occurred_at > last_error_at
|
|
65
|
-
|
|
57
|
+
@last_error = failure
|
|
66
58
|
end
|
|
67
59
|
|
|
68
|
-
|
|
69
|
-
|
|
60
|
+
@consecutive_errors += 1
|
|
61
|
+
@consecutive_successes = 0
|
|
62
|
+
|
|
63
|
+
build_metrics_snapshot
|
|
70
64
|
end
|
|
71
65
|
end
|
|
72
66
|
|
|
73
67
|
def clear
|
|
74
|
-
mutex.synchronize do
|
|
68
|
+
@mutex.synchronize do
|
|
75
69
|
initialize_metrics
|
|
76
70
|
end
|
|
77
71
|
end
|
|
78
72
|
|
|
79
73
|
private
|
|
80
74
|
|
|
81
|
-
attr_accessor :consecutive_errors
|
|
82
|
-
attr_accessor :consecutive_successes
|
|
83
|
-
attr_accessor :last_error
|
|
84
|
-
attr_accessor :last_success_at
|
|
85
|
-
|
|
86
|
-
attr_reader :mutex
|
|
87
|
-
attr_reader :clock
|
|
88
|
-
|
|
89
75
|
def initialize_metrics
|
|
90
76
|
@consecutive_errors = 0
|
|
91
77
|
@consecutive_successes = 0
|
|
@@ -94,7 +80,18 @@ module Stoplight
|
|
|
94
80
|
end
|
|
95
81
|
|
|
96
82
|
def last_error_at
|
|
97
|
-
last_error&.occurred_at
|
|
83
|
+
@last_error&.occurred_at
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def build_metrics_snapshot
|
|
87
|
+
Domain::MetricsSnapshot.new(
|
|
88
|
+
errors: nil,
|
|
89
|
+
successes: nil,
|
|
90
|
+
consecutive_errors: @consecutive_errors,
|
|
91
|
+
consecutive_successes: @consecutive_successes,
|
|
92
|
+
last_error: @last_error,
|
|
93
|
+
last_success_at: @last_success_at
|
|
94
|
+
)
|
|
98
95
|
end
|
|
99
96
|
end
|
|
100
97
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Memory
|
|
6
|
+
module Storage
|
|
7
|
+
class WindowMetrics
|
|
8
|
+
# Hash-based sliding window for O(1) amortized operations.
|
|
9
|
+
#
|
|
10
|
+
# Maintains a running sum and stores per-second counts in a Hash. Ruby's Hash
|
|
11
|
+
# preserves insertion order (FIFO), allowing efficient removal of expired
|
|
12
|
+
# buckets from the front via +Hash#shift+, with their counts subtracted from
|
|
13
|
+
# the running sum.
|
|
14
|
+
#
|
|
15
|
+
# Performance: O(1) amortized for both reads and writes
|
|
16
|
+
# Memory: Bounded to the number of buckets
|
|
17
|
+
#
|
|
18
|
+
# @note Not thread-safe; synchronization must be handled externally
|
|
19
|
+
# @api private
|
|
20
|
+
class SlidingWindow
|
|
21
|
+
def initialize(clock:, window_size:)
|
|
22
|
+
# A hash mapping time buckets to their counts
|
|
23
|
+
@buckets = Hash.new { |buckets, bucket| buckets[bucket] = 0 }
|
|
24
|
+
# The running sum of all increments in the current window
|
|
25
|
+
@running_sum = 0
|
|
26
|
+
@evicted_through = nil
|
|
27
|
+
@clock = clock
|
|
28
|
+
@window_size = window_size
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Increment the count at the current monotonic second
|
|
32
|
+
def increment
|
|
33
|
+
timestamp = @clock.monotonic_seconds
|
|
34
|
+
slide_window!(timestamp - @window_size)
|
|
35
|
+
@buckets[timestamp.to_i] += 1
|
|
36
|
+
@running_sum += 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def sum_in_window
|
|
40
|
+
slide_window!(@clock.monotonic_seconds - @window_size)
|
|
41
|
+
@running_sum
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def inspect
|
|
45
|
+
"#<#{self.class.name} #{@buckets}>"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def slide_window!(window_start)
|
|
51
|
+
# A bucket is keyed at the second it was written, which for a whole-second window is
|
|
52
|
+
# always past the boundary in force then, so a boundary already evicted can expire
|
|
53
|
+
# nothing.
|
|
54
|
+
boundary = window_start.floor
|
|
55
|
+
return if boundary == @evicted_through
|
|
56
|
+
|
|
57
|
+
@evicted_through = boundary
|
|
58
|
+
|
|
59
|
+
loop do
|
|
60
|
+
timestamp, sum = @buckets.first
|
|
61
|
+
if timestamp.nil? || timestamp > boundary
|
|
62
|
+
break
|
|
63
|
+
else
|
|
64
|
+
@running_sum -= sum.to_i
|
|
65
|
+
@buckets.shift
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -27,28 +27,16 @@ module Stoplight
|
|
|
27
27
|
|
|
28
28
|
# Get metrics for the current light
|
|
29
29
|
def metrics_snapshot
|
|
30
|
-
mutex.synchronize
|
|
31
|
-
window_start = (clock.current_time - @window_size)
|
|
32
|
-
errors = @errors.sum_in_window(window_start)
|
|
33
|
-
successes = @successes.sum_in_window(window_start)
|
|
34
|
-
|
|
35
|
-
Domain::MetricsSnapshot.new(
|
|
36
|
-
errors:,
|
|
37
|
-
successes:,
|
|
38
|
-
consecutive_errors: [@consecutive_errors, errors].min,
|
|
39
|
-
consecutive_successes: [@consecutive_successes, successes].min,
|
|
40
|
-
last_error: @last_error,
|
|
41
|
-
last_success_at: @last_success_at
|
|
42
|
-
)
|
|
43
|
-
end
|
|
30
|
+
@mutex.synchronize { build_metrics_snapshot }
|
|
44
31
|
end
|
|
45
32
|
|
|
46
33
|
# Records successful circuit breaker execution
|
|
47
34
|
def record_success
|
|
48
|
-
mutex.synchronize do
|
|
49
|
-
current_time = clock.current_time
|
|
35
|
+
@mutex.synchronize do
|
|
50
36
|
@successes.increment
|
|
51
37
|
|
|
38
|
+
current_time = @clock.current_time
|
|
39
|
+
|
|
52
40
|
if @last_success_at.nil? || current_time > T.must(@last_success_at)
|
|
53
41
|
@last_success_at = current_time
|
|
54
42
|
end
|
|
@@ -60,10 +48,10 @@ module Stoplight
|
|
|
60
48
|
|
|
61
49
|
# Records failed circuit breaker execution
|
|
62
50
|
def record_failure(exception)
|
|
63
|
-
mutex.synchronize do
|
|
51
|
+
@mutex.synchronize do
|
|
64
52
|
@errors.increment
|
|
65
53
|
|
|
66
|
-
failure = Domain::Failure.from_error(exception, time: clock.current_time)
|
|
54
|
+
failure = Domain::Failure.from_error(exception, time: @clock.current_time)
|
|
67
55
|
last_error_at = @last_error&.occurred_at
|
|
68
56
|
|
|
69
57
|
if last_error_at.nil? || failure.occurred_at > last_error_at
|
|
@@ -72,27 +60,40 @@ module Stoplight
|
|
|
72
60
|
|
|
73
61
|
@consecutive_errors += 1
|
|
74
62
|
@consecutive_successes = 0
|
|
63
|
+
|
|
64
|
+
build_metrics_snapshot
|
|
75
65
|
end
|
|
76
66
|
end
|
|
77
67
|
|
|
78
68
|
def clear
|
|
79
|
-
mutex.synchronize do
|
|
69
|
+
@mutex.synchronize do
|
|
80
70
|
initialize_metrics
|
|
81
71
|
end
|
|
82
72
|
end
|
|
83
73
|
|
|
84
74
|
private
|
|
85
75
|
|
|
86
|
-
attr_reader :mutex
|
|
87
|
-
attr_reader :clock
|
|
88
|
-
|
|
89
76
|
def initialize_metrics
|
|
90
77
|
@consecutive_errors = 0
|
|
91
78
|
@consecutive_successes = 0
|
|
92
79
|
@last_error = nil
|
|
93
80
|
@last_success_at = nil
|
|
94
|
-
@successes =
|
|
95
|
-
@errors =
|
|
81
|
+
@successes = SlidingWindow.new(clock: @clock, window_size: @window_size)
|
|
82
|
+
@errors = SlidingWindow.new(clock: @clock, window_size: @window_size)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def build_metrics_snapshot
|
|
86
|
+
errors = @errors.sum_in_window
|
|
87
|
+
successes = @successes.sum_in_window
|
|
88
|
+
|
|
89
|
+
Domain::MetricsSnapshot.new(
|
|
90
|
+
errors:,
|
|
91
|
+
successes:,
|
|
92
|
+
consecutive_errors: [@consecutive_errors, errors].min,
|
|
93
|
+
consecutive_successes: [@consecutive_successes, successes].min,
|
|
94
|
+
last_error: @last_error,
|
|
95
|
+
last_success_at: @last_success_at
|
|
96
|
+
)
|
|
96
97
|
end
|
|
97
98
|
end
|
|
98
99
|
end
|
|
@@ -13,12 +13,14 @@ module Stoplight
|
|
|
13
13
|
attr_reader :notifier
|
|
14
14
|
# The underlying notifier being wrapped.
|
|
15
15
|
attr_reader :error_notifier
|
|
16
|
+
attr_reader :circuit_breaker
|
|
16
17
|
|
|
17
18
|
# @param notifier The notifier to wrap.
|
|
18
19
|
# @param error_notifier called when wrapped data store fails
|
|
19
|
-
def initialize(notifier:, error_notifier:)
|
|
20
|
+
def initialize(notifier:, error_notifier:, circuit_breaker:)
|
|
20
21
|
@notifier = notifier
|
|
21
22
|
@error_notifier = error_notifier
|
|
23
|
+
@circuit_breaker = circuit_breaker
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
# Sends a notification using the wrapped notifier with fail-safe mechanisms.
|
|
@@ -37,13 +39,6 @@ module Stoplight
|
|
|
37
39
|
def ==(other)
|
|
38
40
|
other.is_a?(self.class) && notifier == other.notifier
|
|
39
41
|
end
|
|
40
|
-
|
|
41
|
-
private def circuit_breaker
|
|
42
|
-
@circuit_breaker ||= Stoplight.system_light(
|
|
43
|
-
"stoplight:notifier:fail_safe:#{notifier.class.name}",
|
|
44
|
-
notifiers: []
|
|
45
|
-
)
|
|
46
|
-
end
|
|
47
42
|
end
|
|
48
43
|
end
|
|
49
44
|
end
|
|
@@ -8,7 +8,7 @@ module Stoplight
|
|
|
8
8
|
#
|
|
9
9
|
# @example Custom Notifier Implementation and Usage
|
|
10
10
|
# # Custom notifier that writes notifications to a file
|
|
11
|
-
# class FileNotifier
|
|
11
|
+
# class FileNotifier
|
|
12
12
|
# include Stoplight::Notifier::Generic
|
|
13
13
|
#
|
|
14
14
|
# def initialize(file_path)
|
|
@@ -69,11 +69,11 @@ module Stoplight
|
|
|
69
69
|
|
|
70
70
|
# Processes the notification message.
|
|
71
71
|
#
|
|
72
|
-
# :
|
|
72
|
+
# simplecov:disable
|
|
73
73
|
def put(message)
|
|
74
74
|
raise NotImplementedError
|
|
75
75
|
end
|
|
76
|
-
# :
|
|
76
|
+
# simplecov:enable
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Stoplight
|
|
4
|
+
module Infrastructure
|
|
5
|
+
module Redis
|
|
6
|
+
# Immutable key namespace for a light within a system.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# key_space #=> "stoplight:v6:df384ae97c77:{cfe6861fa39e}"
|
|
10
|
+
# key_space.join(:locks, :recovery) #=> "stoplight:v6:df384ae97c77:{cfe6861fa39e}:locks:recovery"
|
|
11
|
+
class Key < String
|
|
12
|
+
def initialize(*segments)
|
|
13
|
+
super(segments.join(":"))
|
|
14
|
+
freeze
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Builds a Redis key.
|
|
18
|
+
#
|
|
19
|
+
# @param segments to append
|
|
20
|
+
# @return Full Redis key
|
|
21
|
+
def join(*segments) = Key.new(self, *segments)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
-- Returns current time in milliseconds since epoch.
|
|
2
|
+
-- In production: calls redis.call("TIME") for server time.
|
|
3
|
+
-- In tests: overridden with test double that reads from a Redis key.
|
|
4
|
+
local function now()
|
|
5
|
+
local time = redis.call("TIME")
|
|
6
|
+
local seconds = tonumber(time[1])
|
|
7
|
+
local microseconds = tonumber(time[2])
|
|
8
|
+
return seconds * 1000 + math.floor(microseconds / 1000)
|
|
9
|
+
end
|
|
@@ -39,11 +39,9 @@ module Stoplight
|
|
|
39
39
|
recovery_lock if acquired
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
# @param recovery_lock [Stoplight::Infrastructure::Redis::DataStore::RecoveryLockToken]
|
|
43
|
-
# @return [void]
|
|
44
42
|
def release_lock(recovery_lock)
|
|
45
43
|
scripting.call(
|
|
46
|
-
|
|
44
|
+
"recovery_lock/release_lock",
|
|
47
45
|
keys: [lock_key], args: [recovery_lock.token]
|
|
48
46
|
)
|
|
49
47
|
end
|
|
@@ -55,7 +53,7 @@ module Stoplight
|
|
|
55
53
|
attr_reader :scripting
|
|
56
54
|
attr_reader :key_space
|
|
57
55
|
|
|
58
|
-
def lock_key = key_space.
|
|
56
|
+
def lock_key = key_space.join("locks", "recovery")
|
|
59
57
|
def lock_timeout = config.cool_off_time_in_milliseconds
|
|
60
58
|
end
|
|
61
59
|
end
|