Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,11 @@ light = Stoplight("Payment Service")
You can also provide settings during creation:

```ruby
data_store = Stoplight::DataStore::Redis.new(Redis.new)

light = Stoplight("Payment Service",
window_size: 300, # Only count errors in the last five minutes
threshold: 5, # 5 errors before turning red
cool_off_time: 60, # Wait 60 seconds before attempting recovery
recovery_threshold: 1, # 1 successful attempt to turn green again
data_store: data_store, # Use Redis for persistence
tracked_errors: [TimeoutError], # Only count TimeoutError
skipped_errors: [ValidationError] # Ignore ValidationError
)
Expand Down
6 changes: 5 additions & 1 deletion UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## Stoplight 6.0
- Removed Light#with() method
- Removed Ligh's `#with_data_store`, `#with_cool_off_time`, `#with_threshold`, `#with_window_size`, `#with_notifiers`, `#with_error_notifier`, `#with_tracked_errors`, `#with_skipped_errors`
- Removed Light's `#with_data_store`, `#with_cool_off_time`, `#with_threshold`, `#with_window_size`, `#with_notifiers`,
`#with_error_notifier`, `#with_tracked_errors`, `#with_skipped_errors`
- Remove `data_store`, `notifiers`, and `error_notifier` parameters from `Stoplight()` and `Stoplight.light()` methods.
From now on this could be configured only on the Stoplight/System level

## Stoplight 5.0

Stoplight 5.0 introduces several breaking changes, so you'll need to set aside some time to update your code. The good
Expand Down
5 changes: 4 additions & 1 deletion bench/compare_redis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
require "redis"

redis = Redis.new
cashed_stoplight = Stoplight(SecureRandom.uuid, data_store: Stoplight::DataStore::Redis.new(redis), threshold: 10)
Stoplight.configure do |config|
config.data_store = Stoplight::DataStore::Redis.new(redis)
end
cashed_stoplight = Stoplight(SecureRandom.uuid, threshold: 10)

Benchmark.ips do |b|
b.report("after") { cashed_stoplight.run(->(_) {}) { raise if rand(11) % 10 == 1 } }
Expand Down
2 changes: 1 addition & 1 deletion features/stoplight/support/configure_light_world.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def configure_light(name, table = nil)
factory_method = ENV.fetch("STOPLIGHT_LIGHT_CREATION", "Stoplight()")
case factory_method
when "Stoplight()"
Stoplight(name, notifiers:, data_store:, **collect_settings(table))
Stoplight(name, **collect_settings(table))
when "System#light"
system.light(name, **collect_settings(table))
else
Expand Down
24 changes: 13 additions & 11 deletions features/stoplight/support/stoplight_world.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,21 @@ def reset!
@last_exception = nil
@last_result = nil
@last_fallback_received_argument = :nothing
@data_store = case ENV.fetch("STOPLIGHT_DATA_STORE", "Memory")
when "Memory"
Stoplight::DataStore::Memory.new
when "Redis"
redis = Redis.new(url: ENV.fetch("STOPLIGHT_REDIS_URL", "redis://127.0.0.1:6379/0"))
Stoplight.configure(trust_me_im_an_engineer: true) do |config|
config.data_store = case ENV.fetch("STOPLIGHT_DATA_STORE", "Memory")
when "Memory"
Stoplight::DataStore::Memory.new
when "Redis"
redis = Redis.new(url: ENV.fetch("STOPLIGHT_REDIS_URL", "redis://127.0.0.1:6379/0"))

DatabaseCleaner[:redis].db = redis
DatabaseCleaner.clean_with(:deletion)
Stoplight::DataStore::Redis.new(redis)
else
raise ArgumentError, "unexpected data store"
DatabaseCleaner[:redis].db = redis
DatabaseCleaner.clean_with(:deletion)
Stoplight::DataStore::Redis.new(redis)
else
raise ArgumentError, "unexpected data store"
end
config.notifiers = [TestNotifier.new(notifications)]
end
@notifiers = [TestNotifier.new(notifications)]
end

def system = @system ||= Stoplight.__stoplight__system(SecureRandom.uuid, notifiers:, data_store:)
Expand Down
59 changes: 8 additions & 51 deletions lib/stoplight.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,23 +74,17 @@ def system_light(
window_size: T.undefined,
tracked_errors: T.undefined,
skipped_errors: T.undefined,
data_store: T.undefined,
error_notifier: T.undefined,
notifiers: T.undefined,
traffic_control: T.undefined,
traffic_recovery: T.undefined
)
Wiring::LightFactory.new(config: Wiring::DefaultConfig).with(
Wiring::LightFactory.new(config: Wiring::FailSafeConfig).with(
name: "__stoplight__#{name}",
cool_off_time:,
threshold:,
recovery_threshold:,
window_size:,
tracked_errors:,
skipped_errors:,
data_store:,
error_notifier:,
notifiers:,
traffic_control:,
traffic_recovery:
).build
Expand All @@ -108,9 +102,6 @@ def light(
window_size: T.undefined,
tracked_errors: T.undefined,
skipped_errors: T.undefined,
data_store: T.undefined,
error_notifier: T.undefined,
notifiers: T.undefined,
traffic_control: T.undefined,
traffic_recovery: T.undefined
)
Expand All @@ -122,9 +113,6 @@ def light(
window_size:,
tracked_errors:,
skipped_errors:,
data_store:,
error_notifier:,
notifiers:,
traffic_control:,
traffic_recovery:
).build
Expand All @@ -135,10 +123,6 @@ def light(
# Systems are composition roots that own infrastructure (data store, notifiers)
# and enforce configuration consistency for all lights created within them.
#
# @param name [String] Unique identifier for the system
# @param settings [Hash] Configuration options that override global defaults.
# @see Stoplight() documentation
#
# @return [Stoplight::Wiring::System] A new system instance.
#
# @raise [ArgumentError] If a system with the given name already exists.
Expand Down Expand Up @@ -261,29 +245,22 @@ def __stoplight__default_configuration
end
end

# Creates a new Stoplight circuit brNeaker with the given name and settings.
# Creates a new Stoplight circuit breaker with the given name and settings.
#
# @param name [String] The name of the circuit breaker.
# @param settings [Hash] Optional settings to configure the circuit breaker.
# @option settings [Numeric] :cool_off_time The time to wait before resetting the circuit breaker.
# @option settings [Stoplight::DataStore::Base] :data_store The data store to use for storing state.
# @option settings [Array<Stoplight::Notifier::Base>] :notifiers A list of notifiers to use.
# @option settings [Numeric] :threshold The failure threshold to trip the circuit breaker.
# @option settings [Numeric] :window_size The size of the rolling window for failure tracking.
# @option settings [Array<StandardError>] :tracked_errors A list of errors to track.
# @option settings [Array<Exception>] :skipped_errors A list of errors to skip.
# @option settings [Symbol, {Symbol, Hash{Symbol, any}}] :traffic_control The
# traffic control strategy to use.
# @param cool_off_time The time to wait before resetting the circuit breaker.
# @param threshold The failure threshold to trip the circuit breaker.
# @param window_size The size of the rolling window for failure tracking.
# @param tracked_errors A list of errors to track.
# @param skipped_errors A list of errors to skip.
# @param traffic_control The traffic control strategy to use.
#
# @return [Stoplight::Light] A new circuit breaker instance.
# @raise [ArgumentError] If an unknown option is provided in the settings.
#
# @example configure circuit breaker behavior
# light = Stoplight("Payment API", window_size: 300, threshold: 5, cool_off_time: 60)
#
# @example configure data store
# light = Stoplight("Payment API", data_store: Stoplight::DataStore::Redis.new(redis_client))
#
# In the example below, the +TimeoutError+ and +NetworkError+ exceptions
# will be counted towards the threshold for moving the circuit breaker into the red state.
# If not configured, the default tracked error is +StandardError+.
Expand Down Expand Up @@ -313,26 +290,9 @@ def Stoplight(
window_size: Stoplight::T.undefined,
tracked_errors: Stoplight::T.undefined,
skipped_errors: Stoplight::T.undefined,
data_store: Stoplight::T.undefined,
error_notifier: Stoplight::T.undefined,
notifiers: Stoplight::T.undefined,
traffic_control: Stoplight::T.undefined,
traffic_recovery: Stoplight::T.undefined
) # rubocop:disable Naming/MethodName
Stoplight::Common::Deprecations.deprecate(<<~MSG) if error_notifier != Stoplight::T.undefined
Passing "error_notifier" to Stoplight('#{name}') is deprecated and will be removed in v6.0.0.

IMPORTANT: The `error_notifier` is NOT called for exceptions in your protected code.
It only reports internal Stoplight failures (e.g., Redis connection errors).

To fix: Move `error_notifier` to global configuration:

Stoplight.configure do |config|
config.error_notifier = ->(error) { Logger.warn(error) }
end

See: https://github.com/bolshakov/stoplight#error-notifiers
MSG
Stoplight.light(
name,
cool_off_time:,
Expand All @@ -341,9 +301,6 @@ def Stoplight(
window_size:,
tracked_errors:,
skipped_errors:,
data_store:,
error_notifier:,
notifiers:,
traffic_control:,
traffic_recovery:
)
Expand Down
5 changes: 1 addition & 4 deletions lib/stoplight/infrastructure/notifier/fail_safe.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,7 @@ def ==(other)
end

private def circuit_breaker
@circuit_breaker ||= Stoplight.system_light(
"stoplight:notifier:fail_safe:#{notifier.class.name}",
notifiers: []
)
@circuit_breaker ||= Stoplight.system_light("stoplight:notifier:fail_safe:#{notifier.class.name}")
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion lib/stoplight/wiring/default_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ def notifiers = @config.notifiers

# Builds and validates configuration
def to_config!
LegacyConfigurationDsl.new(
SystemConfigurationDsl.new(
name: "default",
cool_off_time: @cool_off_time,
threshold: @threshold,
recovery_threshold: @recovery_threshold,
Expand Down
7 changes: 7 additions & 0 deletions lib/stoplight/wiring/fail_safe_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Stoplight
module Wiring
FailSafeConfig = DefaultConfig.with(notifiers: [])
end
end
69 changes: 0 additions & 69 deletions lib/stoplight/wiring/legacy_configuration_dsl.rb

This file was deleted.

20 changes: 8 additions & 12 deletions lib/stoplight/wiring/light_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ module Wiring
# data store instance:
#
# data_store = Stoplight::DataStore::Memory.new
# light1 = Stoplight("foo", data_store: data_store)
# light2 = Stoplight("bar", data_store: data_store)
# system1 = Stoplight.system("API", data_store: data_store)
# light1 = system.light("foo")
# light2 = Stoplight("bar")
# # light1 and light2 share the same underlying memory store
#
# light3 = Stoplight("baz", data_store: Stoplight::DataStore::Memory.new)
# system2 = Stoplight.system("Payments", data_store: Stoplight::DataStore::Memory.new)
# light3 = system2.light("baz")
# # light3 has its own independent store
#
# This singleton behavior is keyed by config object identity (object_id),
Expand Down Expand Up @@ -64,21 +66,18 @@ def build
end

def with(
name: T.undefined,
name:,
cool_off_time: T.undefined,
threshold: T.undefined,
recovery_threshold: T.undefined,
window_size: T.undefined,
tracked_errors: T.undefined,
skipped_errors: T.undefined,
data_store: T.undefined,
error_notifier: T.undefined,
notifiers: T.undefined,
traffic_control: T.undefined,
traffic_recovery: T.undefined
)
self.class.new(
config: LegacyConfigurationDsl.new(
config: LightConfigurationDsl.new(
name:,
cool_off_time:,
threshold:,
Expand All @@ -87,10 +86,7 @@ def with(
tracked_errors:,
skipped_errors:,
traffic_control:,
traffic_recovery:,
error_notifier:,
data_store:,
notifiers:
traffic_recovery:
).configure!(config)
)
end
Expand Down
3 changes: 0 additions & 3 deletions sig/_private/stoplight.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ module Stoplight
?window_size: optional[duration?],
?tracked_errors: optional[Array[_ExceptionMatcher]],
?skipped_errors: optional[Array[_ExceptionMatcher]],
?data_store: optional[data_store],
?error_notifier: optional[error_notifier],
?notifiers: optional[Array[state_transition_notifier]],
?traffic_control: optional[traffic_control],
?traffic_recovery: optional[traffic_recovery],
) -> Domain::Light
Expand Down
7 changes: 7 additions & 0 deletions sig/_private/stoplight/wiring/fail_safe_config.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

module Stoplight
module Wiring
FailSafeConfig: Domain::Config
end
end
Loading
Loading