semian 0.18.1 → 0.19.1

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: f6bae2b0df6fceb44b91a57900ba40be6b53588866025c269627b08c2ccfa193
4
+ data.tar.gz: f11b8fb67e10cfbb5f1fe393225a45ba0836324768e5fa01366f760151b635b3
5
5
  SHA512:
6
- metadata.gz: 1bd71610d96984ada73fe625966f66ecc13cc921ac659a78c9010eec952f61e1ea56aa4c9f3e6de0572a2b2059e0cd919e1063907c4d39c9d630dd536bf4a89e
7
- data.tar.gz: 20513585134a89dc3a33bcc2895728a64fa5e35e978dbcf83ac44fccc00ae9428e0f8bd8988fe3941e4236d4d18daed380014843b0ad071f945164e68eda68af
6
+ metadata.gz: b39cc0bfcf4341bd0a008f50109abb77f27b75021c28245d4c8e4b2af3bb78ca4611deef872c3b40714d4047a8ede6bcfc88cd426a8866f2e51249f3606aefb4
7
+ data.tar.gz: 89829e6fb002b9d96c2b96ede7339e10e7d3e1c6fee8d8d023b831747e969622d08342e07d7e0cd7ce31f433239e984b881417140e15a9a736501a62c941c79b
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
@@ -47,16 +47,16 @@ module Semian
47
47
  super
48
48
  end
49
49
 
50
- def execute(sql, *)
50
+ def raw_execute(sql, *)
51
51
  if query_allowlisted?(sql)
52
52
  super
53
53
  else
54
- acquire_semian_resource(adapter: :trilogy_adapter, scope: :execute) do
54
+ acquire_semian_resource(adapter: :trilogy_adapter, scope: :query) do
55
55
  super
56
56
  end
57
57
  end
58
58
  end
59
- ruby2_keywords :execute
59
+ ruby2_keywords :raw_execute
60
60
 
61
61
  def active?
62
62
  acquire_semian_resource(adapter: :trilogy_adapter, scope: :ping) do
@@ -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.1"
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.1
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-16 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
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
- rubygems_version: 3.4.12
82
+ rubygems_version: 3.4.13
83
83
  signing_key:
84
84
  specification_version: 4
85
85
  summary: Bulkheading for Ruby with SysV semaphores