semian 0.27.0 → 0.27.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +4 -0
- data/lib/semian/circuit_breaker.rb +19 -2
- data/lib/semian/configuration_validator.rb +51 -0
- data/lib/semian/version.rb +1 -1
- data/lib/semian.rb +15 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b84efc35cf9e47382fc7ac82d69ff0bfb55028eb79c8e4d76127b1e4ee8b7053
|
|
4
|
+
data.tar.gz: 0e16ab39a45133600134574abd65012dae026cfa1f1020695974edf46d00d41d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: abb82539122c2b4ef05420bc996d67cbcd0da9cafd3c735cd85547dd3691af6a6e287f52034d9e2870195ee13708f94e3edf68ad11962e2ad0b6c9023e4243ec
|
|
7
|
+
data.tar.gz: 9041cb2e5834339f584c951558ea071dfd91afc1502971495ab55890963c0ce401ac5b7b1b51611f1d51a3d81618dafb2f2ab648ec8811d7f20f999face6653d
|
data/README.md
CHANGED
|
@@ -588,6 +588,10 @@ There are four configuration parameters for circuit breakers in Semian:
|
|
|
588
588
|
Defaults to `error_timeout` seconds if not set.
|
|
589
589
|
- **error_timeout**. The amount of time in seconds until trying to query the resource
|
|
590
590
|
again.
|
|
591
|
+
- **exponential_backoff_error_timeout**. If set to `true`, we will progress towards error_timeout exponentially, instead of committing to it directly.
|
|
592
|
+
This is useful to avoid rejecting too many requests if the dependency is not really degraded.
|
|
593
|
+
- **exponential_backoff_initial_timeout**. Where to start the exponential backoff towards `error_timeout` from. Defaults to 1 second.
|
|
594
|
+
- **exponential_backoff_multiplier**. The exponential multiplier to use during the exponential backoff towards the `error_timeout`. Defaults to 2.
|
|
591
595
|
- **error_threshold_timeout_enabled**. If set to false it will disable
|
|
592
596
|
the time window for evicting old exceptions. `error_timeout` is still used and
|
|
593
597
|
will reset the circuit. Defaults to `true` if not set.
|
|
@@ -13,12 +13,16 @@ module Semian
|
|
|
13
13
|
:state,
|
|
14
14
|
:last_error,
|
|
15
15
|
:error_threshold_timeout_enabled,
|
|
16
|
+
:exponential_backoff_error_timeout,
|
|
17
|
+
:exponential_backoff_initial_timeout,
|
|
18
|
+
:exponential_backoff_multiplier,
|
|
16
19
|
)
|
|
17
20
|
|
|
18
21
|
def initialize(name, exceptions:, success_threshold:, error_threshold:,
|
|
19
22
|
error_timeout:, implementation:, half_open_resource_timeout: nil,
|
|
20
23
|
error_threshold_timeout: nil, error_threshold_timeout_enabled: true,
|
|
21
|
-
lumping_interval: 0
|
|
24
|
+
lumping_interval: 0, exponential_backoff_error_timeout: false,
|
|
25
|
+
exponential_backoff_initial_timeout: 1, exponential_backoff_multiplier: 2)
|
|
22
26
|
@name = name.to_sym
|
|
23
27
|
@success_count_threshold = success_threshold
|
|
24
28
|
@error_count_threshold = error_threshold
|
|
@@ -28,6 +32,10 @@ module Semian
|
|
|
28
32
|
@exceptions = exceptions
|
|
29
33
|
@half_open_resource_timeout = half_open_resource_timeout
|
|
30
34
|
@lumping_interval = lumping_interval
|
|
35
|
+
@exponential_backoff_error_timeout = exponential_backoff_error_timeout
|
|
36
|
+
@exponential_backoff_initial_timeout = exponential_backoff_initial_timeout
|
|
37
|
+
@exponential_backoff_multiplier = exponential_backoff_multiplier
|
|
38
|
+
@current_error_timeout = exponential_backoff_error_timeout ? exponential_backoff_initial_timeout : error_timeout
|
|
31
39
|
|
|
32
40
|
@errors = implementation::SlidingWindow.new(max_size: @error_count_threshold)
|
|
33
41
|
@successes = implementation::Integer.new
|
|
@@ -102,6 +110,8 @@ module Semian
|
|
|
102
110
|
log_state_transition(:closed)
|
|
103
111
|
@state.close!
|
|
104
112
|
@errors.clear
|
|
113
|
+
# Reset exponential backoff when circuit closes
|
|
114
|
+
@current_error_timeout = @exponential_backoff_error_timeout ? @exponential_backoff_initial_timeout : @error_timeout
|
|
105
115
|
end
|
|
106
116
|
|
|
107
117
|
def transition_to_open
|
|
@@ -115,6 +125,10 @@ module Semian
|
|
|
115
125
|
log_state_transition(:half_open)
|
|
116
126
|
@state.half_open!
|
|
117
127
|
@successes.reset
|
|
128
|
+
# Multiply the backoff timeout when circuit opens (up to the max error_timeout)
|
|
129
|
+
if @exponential_backoff_error_timeout && @current_error_timeout < @error_timeout
|
|
130
|
+
@current_error_timeout = [@current_error_timeout * @exponential_backoff_multiplier, @error_timeout].min
|
|
131
|
+
end
|
|
118
132
|
end
|
|
119
133
|
|
|
120
134
|
def success_threshold_reached?
|
|
@@ -129,7 +143,7 @@ module Semian
|
|
|
129
143
|
last_error_time = @errors.last
|
|
130
144
|
return false unless last_error_time
|
|
131
145
|
|
|
132
|
-
last_error_time + @
|
|
146
|
+
last_error_time + @current_error_timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
133
147
|
end
|
|
134
148
|
|
|
135
149
|
def push_error(error)
|
|
@@ -153,6 +167,9 @@ module Semian
|
|
|
153
167
|
str += " success_count_threshold=#{@success_count_threshold}"
|
|
154
168
|
str += " error_count_threshold=#{@error_count_threshold}"
|
|
155
169
|
str += " error_timeout=#{@error_timeout} error_last_at=\"#{@errors.last}\""
|
|
170
|
+
if @exponential_backoff_error_timeout
|
|
171
|
+
str += " current_error_timeout=#{@current_error_timeout}"
|
|
172
|
+
end
|
|
156
173
|
str += " name=\"#{@name}\""
|
|
157
174
|
if new_state == :open && @last_error
|
|
158
175
|
str += " last_error_message=#{@last_error.message.inspect}"
|
|
@@ -103,6 +103,7 @@ module Semian
|
|
|
103
103
|
error_threshold = @configuration[:error_threshold]
|
|
104
104
|
lumping_interval = @configuration[:lumping_interval]
|
|
105
105
|
half_open_resource_timeout = @configuration[:half_open_resource_timeout]
|
|
106
|
+
exponential_backoff_error_timeout = @configuration[:exponential_backoff_error_timeout]
|
|
106
107
|
|
|
107
108
|
unless error_timeout.is_a?(Numeric) && error_timeout > 0
|
|
108
109
|
err = "error_timeout must be a positive number, got #{error_timeout}"
|
|
@@ -174,6 +175,56 @@ module Semian
|
|
|
174
175
|
|
|
175
176
|
raise_or_log_validation_required!(err)
|
|
176
177
|
end
|
|
178
|
+
|
|
179
|
+
unless exponential_backoff_error_timeout.nil? || [true, false].include?(exponential_backoff_error_timeout)
|
|
180
|
+
err = "exponential_backoff_error_timeout must be a boolean, got #{exponential_backoff_error_timeout}"
|
|
181
|
+
err += hint_format("Use true to enable exponential backoff for error timeout. Use false to disable.")
|
|
182
|
+
|
|
183
|
+
raise_or_log_validation_required!(err)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Validate exponential backoff initial timeout
|
|
187
|
+
exponential_backoff_initial_timeout = @configuration[:exponential_backoff_initial_timeout]
|
|
188
|
+
unless exponential_backoff_initial_timeout.nil? || (exponential_backoff_initial_timeout.is_a?(Numeric) && exponential_backoff_initial_timeout > 0)
|
|
189
|
+
err = "exponential_backoff_initial_timeout must be a positive number, got #{exponential_backoff_initial_timeout}"
|
|
190
|
+
err += hint_format("This is the initial timeout when exponential backoff is enabled. Must be less than error_timeout.")
|
|
191
|
+
|
|
192
|
+
raise_or_log_validation_required!(err)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Validate exponential backoff multiplier
|
|
196
|
+
exponential_backoff_multiplier = @configuration[:exponential_backoff_multiplier]
|
|
197
|
+
unless exponential_backoff_multiplier.nil? || (exponential_backoff_multiplier.is_a?(Numeric) && exponential_backoff_multiplier > 1)
|
|
198
|
+
err = "exponential_backoff_multiplier must be a number greater than 1, got #{exponential_backoff_multiplier}"
|
|
199
|
+
err += hint_format("This is the factor by which the timeout increases on each subsequent opening. Common values are 2 (double) or 1.5.")
|
|
200
|
+
|
|
201
|
+
raise_or_log_validation_required!(err)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Ensure exponential backoff parameters are only provided when exponential_backoff_error_timeout is true
|
|
205
|
+
unless exponential_backoff_error_timeout
|
|
206
|
+
if exponential_backoff_initial_timeout
|
|
207
|
+
err = "exponential_backoff_initial_timeout can only be specified when exponential_backoff_error_timeout is true"
|
|
208
|
+
err += hint_format("Set exponential_backoff_error_timeout: true to use exponential backoff features.")
|
|
209
|
+
|
|
210
|
+
raise_or_log_validation_required!(err)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
if exponential_backoff_multiplier
|
|
214
|
+
err = "exponential_backoff_multiplier can only be specified when exponential_backoff_error_timeout is true"
|
|
215
|
+
err += hint_format("Set exponential_backoff_error_timeout: true to use exponential backoff features.")
|
|
216
|
+
|
|
217
|
+
raise_or_log_validation_required!(err)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Ensure initial timeout is less than error_timeout when using exponential backoff
|
|
222
|
+
if exponential_backoff_error_timeout && exponential_backoff_initial_timeout && exponential_backoff_initial_timeout >= error_timeout
|
|
223
|
+
err = "exponential_backoff_initial_timeout (#{exponential_backoff_initial_timeout}) must be less than error_timeout (#{error_timeout})"
|
|
224
|
+
err += hint_format("The initial timeout should be smaller than the maximum timeout for exponential backoff to be effective.")
|
|
225
|
+
|
|
226
|
+
raise_or_log_validation_required!(err)
|
|
227
|
+
end
|
|
177
228
|
end
|
|
178
229
|
|
|
179
230
|
def validate_quota!(quota)
|
data/lib/semian/version.rb
CHANGED
data/lib/semian.rb
CHANGED
|
@@ -197,6 +197,18 @@ module Semian
|
|
|
197
197
|
# +exceptions+: An array of exception classes that should be accounted as resource errors. Default [].
|
|
198
198
|
# (circuit breaker)
|
|
199
199
|
#
|
|
200
|
+
# +exponential_backoff_error_timeout+: When set to true, instead of opening the circuit for the full
|
|
201
|
+
# error_timeout duration, it starts with a smaller timeout and increases exponentially on each subsequent
|
|
202
|
+
# opening up to error_timeout. This helps avoid over-opening the circuit for temporary issues.
|
|
203
|
+
# Default false. (circuit breaker)
|
|
204
|
+
#
|
|
205
|
+
# +exponential_backoff_initial_timeout+: The initial timeout in seconds when exponential backoff is enabled.
|
|
206
|
+
# Only valid when exponential_backoff_error_timeout is true. Default 1. (circuit breaker)
|
|
207
|
+
#
|
|
208
|
+
# +exponential_backoff_multiplier+: The factor by which to multiply the timeout on each subsequent opening
|
|
209
|
+
# when exponential backoff is enabled. Only valid when exponential_backoff_error_timeout is true.
|
|
210
|
+
# Default 2. (circuit breaker)
|
|
211
|
+
#
|
|
200
212
|
# Returns the registered resource.
|
|
201
213
|
def register(name, **options)
|
|
202
214
|
return UnprotectedResource.new(name) if ENV.key?("SEMIAN_DISABLED")
|
|
@@ -323,6 +335,9 @@ module Semian
|
|
|
323
335
|
end,
|
|
324
336
|
exceptions: Array(exceptions) + [::Semian::BaseError],
|
|
325
337
|
half_open_resource_timeout: options[:half_open_resource_timeout],
|
|
338
|
+
exponential_backoff_error_timeout: options[:exponential_backoff_error_timeout] || false,
|
|
339
|
+
exponential_backoff_initial_timeout: options[:exponential_backoff_initial_timeout] || 1,
|
|
340
|
+
exponential_backoff_multiplier: options[:exponential_backoff_multiplier] || 2,
|
|
326
341
|
implementation: implementation(**options),
|
|
327
342
|
)
|
|
328
343
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: semian
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.27.
|
|
4
|
+
version: 0.27.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Scott Francis
|
|
@@ -94,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '0'
|
|
96
96
|
requirements: []
|
|
97
|
-
rubygems_version: 4.0.
|
|
97
|
+
rubygems_version: 4.0.6
|
|
98
98
|
specification_version: 4
|
|
99
99
|
summary: Bulkheading for Ruby with SysV semaphores
|
|
100
100
|
test_files: []
|