semian 0.24.0 → 0.25.0
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 +80 -5
- data/lib/semian/circuit_breaker.rb +0 -4
- data/lib/semian/configuration_validator.rb +233 -0
- data/lib/semian/lru_hash.rb +2 -2
- data/lib/semian/version.rb +1 -1
- data/lib/semian.rb +6 -14
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c46f4408d72d0fe86b9d1199429942007e48dc5a43dc75dc8f361d3d85e369c9
|
4
|
+
data.tar.gz: c297a18a78e2fc02e3c3823c6488413bc6170d8bd718acdcc8995c96bf68b070
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 115fe761cbc5e3cf7bdaabb2e28ae04c1ac594d9bad3b69b8c63c996d9b52bf2306e6a3de6892a65b069ed4033f0f53ca09f215b19ebde54e4616059bbb6784d
|
7
|
+
data.tar.gz: 94b77f1de5c869ce44384dc4e6b5fb2acbde52f7074a786130b52b48185ff367cd19541da1ecc298828fda06c679f431fe6fd11c7eee9565b882a6fe1432cb7c
|
data/README.md
CHANGED
@@ -113,6 +113,10 @@ Semian.maximum_lru_size = 0
|
|
113
113
|
|
114
114
|
# Minimum time in seconds a resource should be resident in the LRU cache (default: 300s)
|
115
115
|
Semian.minimum_lru_time = 60
|
116
|
+
|
117
|
+
# If true, raise exceptions in case of a validation / constraint failure
|
118
|
+
# Otherwise, log in output
|
119
|
+
Semian.default_force_config_validation = false
|
116
120
|
```
|
117
121
|
|
118
122
|
Note: `minimum_lru_time` is a stronger guarantee than `maximum_lru_size`. That
|
@@ -120,6 +124,10 @@ is, if a resource has been updated more recently than `minimum_lru_time` it
|
|
120
124
|
will not be garbage collected, even if it would cause the LRU cache to grow
|
121
125
|
larger than `maximum_lru_size`.
|
122
126
|
|
127
|
+
Note: `default_force_config_validation` set to `true` is a
|
128
|
+
**_potentially breaking change_**. Misconfigured Semians will raise errors, so
|
129
|
+
make sure that this is what you want. See more in [Configuration Validation](#configuration-validation).
|
130
|
+
|
123
131
|
When instantiating a resource it now needs to be configured for Semian. This is
|
124
132
|
done by passing `semian` as an argument when initializing the client. Examples
|
125
133
|
built in adapters:
|
@@ -132,7 +140,8 @@ client = Mysql2::Client.new(host: "localhost", username: "root", semian: {
|
|
132
140
|
tickets: 8, # See the Understanding Semian section on picking these values
|
133
141
|
success_threshold: 2,
|
134
142
|
error_threshold: 3,
|
135
|
-
error_timeout: 10
|
143
|
+
error_timeout: 10,
|
144
|
+
force_config_validation: false
|
136
145
|
})
|
137
146
|
|
138
147
|
# Redis client
|
@@ -145,6 +154,32 @@ client = Redis.new(semian: {
|
|
145
154
|
})
|
146
155
|
```
|
147
156
|
|
157
|
+
#### Configuration Validation
|
158
|
+
|
159
|
+
Semian now provides a flag to specify log-based and exception-based configuration validation. To
|
160
|
+
explicitly force the Semian to validate it's configurations, pass `force_config_validation: true`
|
161
|
+
into your resource. This will raise an error in the case of a misconfigured or illegal Semian. Otherwise,
|
162
|
+
if it is set to `false`, it will log misconfigured parameters verbosely in output.
|
163
|
+
|
164
|
+
If not specified, it will use `Semian.default_force_config_validation` as
|
165
|
+
the flag.
|
166
|
+
|
167
|
+
##### Migration Strategy for Force Config Validation
|
168
|
+
|
169
|
+
When migrating to use `force_config_validation: true`, follow these steps:
|
170
|
+
|
171
|
+
1. **Deploy with it turned off**: Start with `force_config_validation: false` in your configuration
|
172
|
+
2. **Look for logs with prefix**: Monitor your application logs for entries with the `[SEMIAN_CONFIG_WARNING]:` prefix. These logs will indicate misconfigured Semian resources
|
173
|
+
3. **Iterate to fix**: Address each configuration issue identified in the logs by updating your Semian configurations
|
174
|
+
4. **Enable**: Once all configuration issues are resolved, set `force_config_validation: true` to enable strict validation
|
175
|
+
|
176
|
+
Example log entries to look for:
|
177
|
+
```
|
178
|
+
[SEMIAN_CONFIG_WARNING]: Missing required arguments for Semian: [:success_threshold, :error_threshold, :error_timeout]
|
179
|
+
[SEMIAN_CONFIG_WARNING]: Both bulkhead and circuitbreaker cannot be disabled.
|
180
|
+
[SEMIAN_CONFIG_WARNING]: Bulkhead configuration require either the :tickets or :quota parameter, you provided neither
|
181
|
+
```
|
182
|
+
|
148
183
|
#### Thread Safety
|
149
184
|
|
150
185
|
Semian's circuit breaker implementation is thread-safe by default as of
|
@@ -284,11 +319,11 @@ Semian::NetHTTP.semian_configuration = proc do |host, port|
|
|
284
319
|
SEMIAN_PARAMETERS.merge(name: name)
|
285
320
|
end
|
286
321
|
|
287
|
-
# Two requests to
|
322
|
+
# Two requests to shopify.com can use two different semian resources,
|
288
323
|
# as long as `CurrentSemianSubResource.sub_name` is set accordingly:
|
289
|
-
# CurrentSemianSubResource.set(sub_name: "sub_resource_1") { Net::HTTP.get_response(URI("http://
|
324
|
+
# CurrentSemianSubResource.set(sub_name: "sub_resource_1") { Net::HTTP.get_response(URI("http://shopify.com")) }
|
290
325
|
# and:
|
291
|
-
# CurrentSemianSubResource.set(sub_name: "sub_resource_2") { Net::HTTP.get_response(URI("http://
|
326
|
+
# CurrentSemianSubResource.set(sub_name: "sub_resource_2") { Net::HTTP.get_response(URI("http://shopify.com")) }
|
292
327
|
```
|
293
328
|
|
294
329
|
For most purposes, `"#{host}_#{port}"` is a good default `name`. Custom `name` formats
|
@@ -903,6 +938,46 @@ Running Tests:
|
|
903
938
|
|
904
939
|
- `$ bundle exec rake` Run with `SKIP_FLAKY_TESTS=true` to skip flaky tests (CI runs all tests)
|
905
940
|
|
941
|
+
### Interactive Test Debugging
|
942
|
+
|
943
|
+
To use the interactive debugger on vscode:
|
944
|
+
- Open semian in vscode
|
945
|
+
- Create an `.env` file (if it doesn't exist)
|
946
|
+
- Set up a `DEBUG` ENV variable (ex; `DEBUG=true`)
|
947
|
+
- Under the `.vscode/` subdirectory, create a `launch.json` file, and include the following:
|
948
|
+
|
949
|
+
```json
|
950
|
+
{
|
951
|
+
"configurations": [
|
952
|
+
{
|
953
|
+
"type": "rdbg",
|
954
|
+
"name": "Attach to Ruby rdbg",
|
955
|
+
"request": "attach",
|
956
|
+
"debugPort": "12345",
|
957
|
+
}
|
958
|
+
]
|
959
|
+
}
|
960
|
+
```
|
961
|
+
|
962
|
+
- For universal support, for any lines you would like to add breakpoints to in your `_test.rb` file (under `test/`), include the following snippet near the line of interest:
|
963
|
+
|
964
|
+
```rb
|
965
|
+
require "debug"
|
966
|
+
binding.break if ENV["DEBUG"]
|
967
|
+
```
|
968
|
+
|
969
|
+
**Note:** unless you are using an vscode extension such as [Dev Container](https://code.visualstudio.com/docs/devcontainers/tutorial), **do not use the built-in vscode breakpoints -- they will not work!**
|
970
|
+
|
971
|
+
- Start up the test container
|
972
|
+
|
973
|
+
```shell
|
974
|
+
$ docker-compose -f .devcontainer/docker-compose.yml --profile test up -d
|
975
|
+
```
|
976
|
+
|
977
|
+
- When the process indicates that it is waiting for the debugger connection, go to the `Run and Debug` tab, and execute the `Attach to Ruby rdbg` debugger
|
978
|
+
|
979
|
+
- Use the vscode debugging tools (such as step in, step out, pause, resume) as normal
|
980
|
+
|
906
981
|
## Everything else
|
907
982
|
|
908
983
|
Test semian in containers:
|
@@ -917,7 +992,7 @@ If you make any changes to `.devcontainer/` you'd need to recreate the container
|
|
917
992
|
Run tests in containers:
|
918
993
|
|
919
994
|
```shell
|
920
|
-
$ docker-compose -f ./.devcontainer/docker-compose.yml run --rm test
|
995
|
+
$ docker-compose -f ./.devcontainer/docker-compose.yml --profile test run --rm test
|
921
996
|
```
|
922
997
|
|
923
998
|
Running Tests:
|
@@ -29,10 +29,6 @@ module Semian
|
|
29
29
|
@half_open_resource_timeout = half_open_resource_timeout
|
30
30
|
@lumping_interval = lumping_interval
|
31
31
|
|
32
|
-
if @lumping_interval > @error_threshold_timeout
|
33
|
-
raise ArgumentError, "lumping_interval (#{@lumping_interval}) must be less than error_threshold_timeout (#{@error_threshold_timeout})"
|
34
|
-
end
|
35
|
-
|
36
32
|
@errors = implementation::SlidingWindow.new(max_size: @error_count_threshold)
|
37
33
|
@successes = implementation::Integer.new
|
38
34
|
@state = implementation::State.new
|
@@ -0,0 +1,233 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Semian
|
4
|
+
class ConfigurationValidator
|
5
|
+
def initialize(name, configuration)
|
6
|
+
@name = name
|
7
|
+
@configuration = configuration
|
8
|
+
@adapter = configuration[:adapter]
|
9
|
+
@force_config_validation = force_config_validation?
|
10
|
+
|
11
|
+
unless @force_config_validation
|
12
|
+
Semian.logger.warn(
|
13
|
+
"Semian is running in log-mode for configuration validation. This means that Semian will not raise an error if the configuration is invalid. This is not recommended for production environments.\n\n[IMPORTANT] IN FUTURE RELEASES, STRICT CONFIGURATION VALIDATION WILL BE THE DEFAULT BEHAVIOR. PLEASE UPDATE YOUR CONFIGURATION TO USE `force_config_validation: true` TO ENABLE STRICT CONFIGURATION VALIDATION. ALLOWING MISCONFIGURATIONS IN FUTURE RELEASES WILL BREAK YOUR SEMIAN.\n---\n",
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate!
|
19
|
+
validate_circuit_breaker_or_bulkhead!
|
20
|
+
validate_bulkhead_configuration!
|
21
|
+
validate_circuit_breaker_configuration!
|
22
|
+
validate_resource_name!
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def hint_format(message)
|
28
|
+
"\n\nHINT: #{message}\n---"
|
29
|
+
end
|
30
|
+
|
31
|
+
def raise_or_log_validation_required!(message)
|
32
|
+
if @force_config_validation
|
33
|
+
raise ArgumentError, message
|
34
|
+
else
|
35
|
+
Semian.logger.warn("[SEMIAN_CONFIG_WARNING]: #{message}")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def require_keys!(required, options)
|
40
|
+
diff = required - options.keys
|
41
|
+
unless diff.empty?
|
42
|
+
raise_or_log_validation_required!("Missing required arguments for Semian: #{diff}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_circuit_breaker_or_bulkhead!
|
47
|
+
if (@configuration[:circuit_breaker] == false || ENV.key?("SEMIAN_CIRCUIT_BREAKER_DISABLED")) && (@configuration[:bulkhead] == false || ENV.key?("SEMIAN_BULKHEAD_DISABLED"))
|
48
|
+
raise_or_log_validation_required!("Both bulkhead and circuitbreaker cannot be disabled.")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def validate_bulkhead_configuration!
|
53
|
+
return if ENV.key?("SEMIAN_BULKHEAD_DISABLED")
|
54
|
+
return unless @configuration.fetch(:bulkhead, true)
|
55
|
+
|
56
|
+
tickets = @configuration[:tickets]
|
57
|
+
quota = @configuration[:quota]
|
58
|
+
|
59
|
+
if tickets.nil? && quota.nil?
|
60
|
+
raise_or_log_validation_required!("Bulkhead configuration require either the :tickets or :quota parameter, you provided neither")
|
61
|
+
end
|
62
|
+
|
63
|
+
if tickets && quota
|
64
|
+
raise_or_log_validation_required!("Bulkhead configuration require either the :tickets or :quota parameter, you provided both")
|
65
|
+
end
|
66
|
+
|
67
|
+
validate_quota!(quota) if quota
|
68
|
+
validate_tickets!(tickets) if tickets
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_circuit_breaker_configuration!
|
72
|
+
return if ENV.key?("SEMIAN_CIRCUIT_BREAKER_DISABLED")
|
73
|
+
return unless @configuration.fetch(:circuit_breaker, true)
|
74
|
+
|
75
|
+
require_keys!([:success_threshold, :error_threshold, :error_timeout], @configuration)
|
76
|
+
validate_thresholds!
|
77
|
+
validate_timeouts!
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_thresholds!
|
81
|
+
success_threshold = @configuration[:success_threshold]
|
82
|
+
error_threshold = @configuration[:error_threshold]
|
83
|
+
|
84
|
+
unless success_threshold.is_a?(Integer) && success_threshold > 0
|
85
|
+
err = "success_threshold must be a positive integer, got #{success_threshold}"
|
86
|
+
|
87
|
+
if success_threshold == 0
|
88
|
+
err += hint_format("Are you sure that this is what you want? This will close the circuit breaker immediately after `error_timeout` seconds without checking the resource!")
|
89
|
+
end
|
90
|
+
|
91
|
+
raise_or_log_validation_required!(err)
|
92
|
+
end
|
93
|
+
|
94
|
+
unless error_threshold.is_a?(Integer) && error_threshold > 0
|
95
|
+
err = "error_threshold must be a positive integer, got #{error_threshold}"
|
96
|
+
|
97
|
+
if error_threshold == 0
|
98
|
+
err += hint_format("Are you sure that this is what you want? This can result in the circuit opening up at unpredictable times!")
|
99
|
+
end
|
100
|
+
|
101
|
+
raise_or_log_validation_required!(err)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def validate_timeouts!
|
106
|
+
error_timeout = @configuration[:error_timeout]
|
107
|
+
error_threshold_timeout_enabled = @configuration[:error_threshold_timeout_enabled].nil? ? true : @configuration[:error_threshold_timeout_enabled]
|
108
|
+
error_threshold = @configuration[:error_threshold]
|
109
|
+
lumping_interval = @configuration[:lumping_interval]
|
110
|
+
half_open_resource_timeout = @configuration[:half_open_resource_timeout]
|
111
|
+
|
112
|
+
unless error_timeout.is_a?(Numeric) && error_timeout > 0
|
113
|
+
err = "error_timeout must be a positive number, got #{error_timeout}"
|
114
|
+
|
115
|
+
if error_timeout == 0
|
116
|
+
err += hint_format("Are you sure that this is what you want? This will close the circuit breaker immediately after opening it!")
|
117
|
+
end
|
118
|
+
|
119
|
+
raise_or_log_validation_required!(err)
|
120
|
+
end
|
121
|
+
|
122
|
+
# This state checks for contradictions between error_threshold_timeout_enabled and error_threshold_timeout.
|
123
|
+
unless error_threshold_timeout_enabled || !@configuration[:error_threshold_timeout]
|
124
|
+
err = "error_threshold_timeout_enabled and error_threshold_timeout must not contradict each other, got error_threshold_timeout_enabled: #{error_threshold_timeout_enabled}, error_threshold_timeout: #{@configuration[:error_threshold_timeout]}"
|
125
|
+
err += hint_format("Are you sure this is what you want? This will set error_threshold_timeout_enabled to #{error_threshold_timeout_enabled} while error_threshold_timeout is #{@configuration[:error_threshold_timeout] ? "truthy" : "falsy"}")
|
126
|
+
|
127
|
+
raise_or_log_validation_required!(err)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Only set this after we have checked the error_threshold_timeout_enabled condition
|
131
|
+
error_threshold_timeout = @configuration[:error_threshold_timeout] || error_timeout
|
132
|
+
unless error_threshold_timeout.is_a?(Numeric) && error_threshold_timeout > 0
|
133
|
+
err = "error_threshold_timeout must be a positive number, got #{error_threshold_timeout}"
|
134
|
+
|
135
|
+
if error_threshold_timeout == 0
|
136
|
+
err += hint_format("Are you sure that this is what you want? This will almost never open the circuit breaker since the time interval to catch errors is 0!")
|
137
|
+
end
|
138
|
+
|
139
|
+
raise_or_log_validation_required!(err)
|
140
|
+
end
|
141
|
+
|
142
|
+
unless half_open_resource_timeout.nil? || (half_open_resource_timeout.is_a?(Numeric) && half_open_resource_timeout > 0)
|
143
|
+
err = "half_open_resource_timeout must be a positive number, got #{half_open_resource_timeout}"
|
144
|
+
|
145
|
+
if half_open_resource_timeout == 0
|
146
|
+
err += hint_format("Are you sure that this is what you want? This will never half-open the circuit breaker! If that's what you want, you can omit the option instead")
|
147
|
+
end
|
148
|
+
|
149
|
+
raise_or_log_validation_required!(err)
|
150
|
+
end
|
151
|
+
|
152
|
+
unless lumping_interval.nil? || (lumping_interval.is_a?(Numeric) && lumping_interval > 0)
|
153
|
+
err = "lumping_interval must be a positive number, got #{lumping_interval}"
|
154
|
+
|
155
|
+
if lumping_interval == 0
|
156
|
+
err += hint_format("Are you sure that this is what you want? This will never lump errors! If that's what you want, you can omit the option instead")
|
157
|
+
end
|
158
|
+
|
159
|
+
raise_or_log_validation_required!(err)
|
160
|
+
end
|
161
|
+
|
162
|
+
# You might be wondering why not check just check lumping_interval * error_threshold <= error_threshold_timeout
|
163
|
+
# The reason being is that since the lumping_interval starts at the first error, we count the first error
|
164
|
+
# at second 0. So we need to subtract 1 from the error_threshold to get the correct minimum time to reach the
|
165
|
+
# error threshold. error_threshold_timeout cannot be less than this minimum time.
|
166
|
+
#
|
167
|
+
# For example,
|
168
|
+
#
|
169
|
+
# error_threshold = 3
|
170
|
+
# error_threshold_timeout = 10
|
171
|
+
# lumping_interval = 4
|
172
|
+
#
|
173
|
+
# The first error could be counted at second 0, the second error could be counted at second 4, and the third
|
174
|
+
# error could be counted at second 8. So this is a valid configuration.
|
175
|
+
|
176
|
+
unless lumping_interval.nil? || error_threshold_timeout.nil? || lumping_interval * (error_threshold - 1) <= error_threshold_timeout
|
177
|
+
err = "constraint violated, this circuit breaker can never open! lumping_interval * (error_threshold - 1) should be <= error_threshold_timeout, got lumping_interval: #{lumping_interval}, error_threshold: #{error_threshold}, error_threshold_timeout: #{error_threshold_timeout}"
|
178
|
+
err += hint_format("lumping_interval starts from the first error and not in a fixed window. So you can fit n errors in n-1 seconds, since error 0 starts at 0 seconds. Ensure that you can fit `error_threshold` errors lumped in `lumping_interval` seconds within `error_threshold_timeout` seconds.")
|
179
|
+
|
180
|
+
raise_or_log_validation_required!(err)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def validate_quota!(quota)
|
185
|
+
unless quota.is_a?(Numeric) && quota > 0 && quota < 1
|
186
|
+
err = "quota must be a decimal between 0 and 1, got #{quota}"
|
187
|
+
|
188
|
+
if quota == 0
|
189
|
+
err += hint_format("Are you sure that this is what you want? This is the same as assigning no workers to the resource, disabling the resource!")
|
190
|
+
elsif quota == 1
|
191
|
+
err += hint_format("Are you sure that this is what you want? This is the same as assigning all workers to the resource, disabling the bulkhead!")
|
192
|
+
end
|
193
|
+
|
194
|
+
raise_or_log_validation_required!(err)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def validate_tickets!(tickets)
|
199
|
+
unless tickets.is_a?(Integer) && tickets > 0 && tickets < Semian::MAX_TICKETS
|
200
|
+
err = "ticket count must be a positive integer and less than #{Semian::MAX_TICKETS}, got #{tickets}"
|
201
|
+
|
202
|
+
if tickets == 0
|
203
|
+
err += hint_format("Are you sure that this is what you want? This is the same as assigning no workers to the resource, disabling the resource!")
|
204
|
+
elsif tickets == Semian::MAX_TICKETS
|
205
|
+
err += hint_format("Are you sure that this is what you want? This is the same as assigning all workers to the resource, disabling the bulkhead!")
|
206
|
+
end
|
207
|
+
|
208
|
+
raise_or_log_validation_required!(err)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def validate_resource_name!
|
213
|
+
unless @name.is_a?(String) || @name.is_a?(Symbol)
|
214
|
+
raise_or_log_validation_required!("name must be a symbol or string, got #{@name}")
|
215
|
+
end
|
216
|
+
|
217
|
+
if Semian.resources[@name]
|
218
|
+
err = "Resource with name #{@name} is already registered"
|
219
|
+
err += hint_format("Are you sure that this is what you want? This will override an existing resource with the same name!")
|
220
|
+
|
221
|
+
raise_or_log_validation_required!(err)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def force_config_validation?
|
226
|
+
if @configuration[:force_config_validation].nil?
|
227
|
+
Semian.default_force_config_validation
|
228
|
+
else
|
229
|
+
@configuration[:force_config_validation]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
data/lib/semian/lru_hash.rb
CHANGED
data/lib/semian/version.rb
CHANGED
data/lib/semian.rb
CHANGED
@@ -16,6 +16,7 @@ require "semian/simple_sliding_window"
|
|
16
16
|
require "semian/simple_integer"
|
17
17
|
require "semian/simple_state"
|
18
18
|
require "semian/lru_hash"
|
19
|
+
require "semian/configuration_validator"
|
19
20
|
|
20
21
|
#
|
21
22
|
# === Overview
|
@@ -102,11 +103,12 @@ module Semian
|
|
102
103
|
OpenCircuitError = Class.new(BaseError)
|
103
104
|
SemaphoreMissingError = Class.new(BaseError)
|
104
105
|
|
105
|
-
attr_accessor :maximum_lru_size, :minimum_lru_time, :default_permissions, :namespace
|
106
|
+
attr_accessor :maximum_lru_size, :minimum_lru_time, :default_permissions, :namespace, :default_force_config_validation
|
106
107
|
|
107
108
|
self.maximum_lru_size = 500
|
108
109
|
self.minimum_lru_time = 300 # 300 seconds / 5 minutes
|
109
110
|
self.default_permissions = 0660
|
111
|
+
self.default_force_config_validation = false
|
110
112
|
|
111
113
|
def issue_disabled_semaphores_warning
|
112
114
|
return if defined?(@warning_issued)
|
@@ -184,13 +186,12 @@ module Semian
|
|
184
186
|
def register(name, **options)
|
185
187
|
return UnprotectedResource.new(name) if ENV.key?("SEMIAN_DISABLED")
|
186
188
|
|
189
|
+
# Validate configuration before proceeding
|
190
|
+
ConfigurationValidator.new(name, options).validate!
|
191
|
+
|
187
192
|
circuit_breaker = create_circuit_breaker(name, **options)
|
188
193
|
bulkhead = create_bulkhead(name, **options)
|
189
194
|
|
190
|
-
if circuit_breaker.nil? && bulkhead.nil?
|
191
|
-
raise ArgumentError, "Both bulkhead and circuitbreaker cannot be disabled."
|
192
|
-
end
|
193
|
-
|
194
195
|
resources[name] = ProtectedResource.new(name, bulkhead, circuit_breaker)
|
195
196
|
end
|
196
197
|
|
@@ -296,8 +297,6 @@ module Semian
|
|
296
297
|
return if ENV.key?("SEMIAN_CIRCUIT_BREAKER_DISABLED")
|
297
298
|
return unless options.fetch(:circuit_breaker, true)
|
298
299
|
|
299
|
-
require_keys!([:success_threshold, :error_threshold, :error_timeout], options)
|
300
|
-
|
301
300
|
exceptions = options[:exceptions] || []
|
302
301
|
CircuitBreaker.new(
|
303
302
|
name,
|
@@ -351,13 +350,6 @@ module Semian
|
|
351
350
|
timeout: timeout,
|
352
351
|
)
|
353
352
|
end
|
354
|
-
|
355
|
-
def require_keys!(required, options)
|
356
|
-
diff = required - options.keys
|
357
|
-
unless diff.empty?
|
358
|
-
raise ArgumentError, "Missing required arguments for Semian: #{diff}"
|
359
|
-
end
|
360
|
-
end
|
361
353
|
end
|
362
354
|
|
363
355
|
if Semian.semaphores_enabled?
|
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.
|
4
|
+
version: 0.25.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Francis
|
@@ -36,6 +36,7 @@ files:
|
|
36
36
|
- lib/semian/activerecord_trilogy_adapter.rb
|
37
37
|
- lib/semian/adapter.rb
|
38
38
|
- lib/semian/circuit_breaker.rb
|
39
|
+
- lib/semian/configuration_validator.rb
|
39
40
|
- lib/semian/grpc.rb
|
40
41
|
- lib/semian/instrumentable.rb
|
41
42
|
- lib/semian/lru_hash.rb
|
@@ -77,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
78
|
- !ruby/object:Gem::Version
|
78
79
|
version: '0'
|
79
80
|
requirements: []
|
80
|
-
rubygems_version: 3.
|
81
|
+
rubygems_version: 3.7.1
|
81
82
|
specification_version: 4
|
82
83
|
summary: Bulkheading for Ruby with SysV semaphores
|
83
84
|
test_files: []
|