semian 0.18.1 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8c0cecc8c8e42f223f2cde75e3818ad4bc33897107b6a99186beb48c413c6d30
4
- data.tar.gz: 9202b9e1cf15ff54d68f8d3fe11800ace1624828d1aa840840b88180f65e5355
3
+ metadata.gz: 47d9f8254a2231b2ca3210ff09331ace5bd23f86bfe0f178a95e64187d3f1203
4
+ data.tar.gz: 644bcb5a30040499af851b17a9cb46c7a2a4fe0a3896cfd7052bc39e115fb4bc
5
5
  SHA512:
6
- metadata.gz: 1bd71610d96984ada73fe625966f66ecc13cc921ac659a78c9010eec952f61e1ea56aa4c9f3e6de0572a2b2059e0cd919e1063907c4d39c9d630dd536bf4a89e
7
- data.tar.gz: 20513585134a89dc3a33bcc2895728a64fa5e35e978dbcf83ac44fccc00ae9428e0f8bd8988fe3941e4236d4d18daed380014843b0ad071f945164e68eda68af
6
+ metadata.gz: b3d5769728bd70009178ad78d1b397b5a067e2482578e0916ae76963870cc9808499a49cd5a22f6a3ffa254cbb203f5dec37584352815cb9ba32a8a281432468
7
+ data.tar.gz: 7ea88141334bed5ecf490f25bd3b8d98759de50f38824af6f0af70619f0688eeacea05c80181a079fb3563d887fc1d1dbc6840893eb4a8c78e729a1c4b498b94
data/README.md CHANGED
@@ -254,10 +254,40 @@ The `semian_options` passed apply to that resource. Semian creates the `semian_i
254
254
  from the `name` to look up and store changes in the circuit breaker and bulkhead states
255
255
  and associate successes, failures, errors with the protected resource.
256
256
 
257
- We only require that:
258
- * the `semian_configuration` be **set only once** over the lifetime of the library
259
- * the output of the `proc` be the same over time, that is, the configuration produced by
260
- each pair of `host`, `port` is **the same each time** the callback is invoked.
257
+ We only require that the `semian_configuration` be **set only once** over the lifetime of
258
+ the library.
259
+
260
+ If you need to return different values for the same pair of `host`/`port` value, you **must**
261
+ include the `dynamic: true` option. Returning different values for the same `host`/`port` values
262
+ without setting the `dynamic` option can lead to undesirable behavior.
263
+
264
+ A common example for dynamic options is the use of a thread local variable, such as
265
+ `ActiveSupport::CurrentAttributes`, for requests to a service acting as a proxy.
266
+
267
+ ```ruby
268
+ SEMIAN_PARAMETERS = {
269
+ # ...
270
+ dynamic: true,
271
+ }
272
+
273
+ class CurrentSemianSubResource < ActiveSupport::Attributes
274
+ attribute :name
275
+ end
276
+
277
+ Semian::NetHTTP.semian_configuration = proc do |host, port|
278
+ name = "#{host}_#{port}"
279
+ if (sub_resource_name = CurrentSemianSubResource.name)
280
+ name << "_#{name}"
281
+ end
282
+ SEMIAN_PARAMETERS.merge(name: name)
283
+ end
284
+
285
+ # Two requests to example.com can use two different semian resources,
286
+ # as long as `CurrentSemianSubResource.name` is set accordingly:
287
+ # CurrentSemianSubResource.set(name: "sub_resource_1") { Net::HTTP.get_response(URI("http://example.com")) }
288
+ # and:
289
+ # CurrentSemianSubResource.set(name: "sub_resource_2") { Net::HTTP.get_response(URI("http://example.com")) }
290
+ ```
261
291
 
262
292
  For most purposes, `"#{host}_#{port}"` is a good default `name`. Custom `name` formats
263
293
  can be useful to grouping related subdomains as one resource, so that they all
@@ -9,19 +9,25 @@ module Semian
9
9
  end
10
10
 
11
11
  def semian_resource
12
- @semian_resource ||= case semian_options
12
+ return @semian_resource if @semian_resource
13
+
14
+ case semian_options
13
15
  when false
14
- UnprotectedResource.new(semian_identifier)
16
+ @semian_resource = UnprotectedResource.new(semian_identifier)
15
17
  when nil
16
18
  Semian.logger.info("Semian is not configured for #{self.class.name}: #{semian_identifier}")
17
- UnprotectedResource.new(semian_identifier)
19
+ @semian_resource = UnprotectedResource.new(semian_identifier)
18
20
  else
19
21
  options = semian_options.dup
20
22
  options.delete(:name)
21
23
  options[:consumer] = self
22
24
  options[:exceptions] ||= []
23
25
  options[:exceptions] += resource_exceptions
24
- ::Semian.retrieve_or_register(semian_identifier, **options)
26
+ resource = ::Semian.retrieve_or_register(semian_identifier, **options)
27
+
28
+ @semian_resource = resource unless options.fetch(:dynamic, false)
29
+
30
+ resource
25
31
  end
26
32
  end
27
33
 
@@ -53,7 +59,11 @@ module Semian
53
59
  return @semian_options if defined? @semian_options
54
60
 
55
61
  options = raw_semian_options
56
- @semian_options = options && options.map { |k, v| [k.to_sym, v] }.to_h
62
+
63
+ symbolized_options = options && options.transform_keys(&:to_sym) # rubocop:disable Style/SafeNavigation
64
+ symbolized_options.tap do
65
+ @semian_options = symbolized_options if !symbolized_options || !symbolized_options.fetch(:dynamic, false)
66
+ end
57
67
  end
58
68
 
59
69
  def raw_semian_options
@@ -91,16 +91,20 @@ module Semian
91
91
  end
92
92
 
93
93
  def connect
94
- return super if disabled?
94
+ with_cleared_dynamic_options do
95
+ return super if disabled?
95
96
 
96
- acquire_semian_resource(adapter: :http, scope: :connection) { super }
97
+ acquire_semian_resource(adapter: :http, scope: :connection) { super }
98
+ end
97
99
  end
98
100
 
99
101
  def transport_request(*)
100
- return super if disabled?
102
+ with_cleared_dynamic_options do
103
+ return super if disabled?
101
104
 
102
- acquire_semian_resource(adapter: :http, scope: :query) do
103
- handle_error_responses(super)
105
+ acquire_semian_resource(adapter: :http, scope: :query) do
106
+ handle_error_responses(super)
107
+ end
104
108
  end
105
109
  end
106
110
 
@@ -125,6 +129,24 @@ module Semian
125
129
  end
126
130
  result
127
131
  end
132
+
133
+ def with_cleared_dynamic_options
134
+ unless @resource_acquisition_in_progress
135
+ @resource_acquisition_in_progress = true
136
+ resource_acquisition_started = true
137
+ end
138
+
139
+ yield
140
+ ensure
141
+ if resource_acquisition_started
142
+ if @raw_semian_options&.fetch(:dynamic, false)
143
+ # Clear @raw_semian_options if the resource was flagged as dynamic.
144
+ @raw_semian_options = nil
145
+ end
146
+
147
+ @resource_acquisition_in_progress = false
148
+ end
149
+ end
128
150
  end
129
151
  end
130
152
 
@@ -6,6 +6,14 @@ class Redis
6
6
  BaseConnectionError.include(::Semian::AdapterError)
7
7
  OutOfMemoryError.include(::Semian::AdapterError)
8
8
 
9
+ class ReadOnlyError < Redis::BaseConnectionError
10
+ # A ReadOnlyError is a fast failure and we don't want to track these errors so that we can reconnect
11
+ # to the new primary ASAP
12
+ def marks_semian_circuits?
13
+ false
14
+ end
15
+ end
16
+
9
17
  class SemianError < BaseConnectionError
10
18
  def initialize(semian_identifier, *args)
11
19
  super(*args)
data/lib/semian/redis.rb CHANGED
@@ -32,6 +32,14 @@ class Redis
32
32
  end
33
33
  end
34
34
 
35
+ class ReadOnlyError < Redis::CommandError
36
+ # A ReadOnlyError is a fast failure and we don't want to track these errors so that we can reconnect
37
+ # to the new primary ASAP
38
+ def marks_semian_circuits?
39
+ false
40
+ end
41
+ end
42
+
35
43
  ResourceBusyError = Class.new(SemianError)
36
44
  CircuitOpenError = Class.new(SemianError)
37
45
  ResolveError = Class.new(SemianError)
@@ -14,6 +14,14 @@ class RedisClient
14
14
 
15
15
  OutOfMemoryError.include(::Semian::AdapterError)
16
16
 
17
+ class ReadOnlyError < RedisClient::ConnectionError
18
+ # A ReadOnlyError is a fast failure and we don't want to track these errors so that we can reconnect
19
+ # to the new primary ASAP
20
+ def marks_semian_circuits?
21
+ false
22
+ end
23
+ end
24
+
17
25
  class SemianError < ConnectionError
18
26
  def initialize(semian_identifier, *args)
19
27
  super(*args)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Semian
4
- VERSION = "0.18.1"
4
+ VERSION = "0.19.0"
5
5
  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.18.1
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Francis
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2023-05-02 00:00:00.000000000 Z
13
+ date: 2023-05-03 00:00:00.000000000 Z
14
14
  dependencies: []
15
15
  description: |2
16
16
  A Ruby C extention that is used to control access to shared resources