semian 0.18.0 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +35 -6
- data/lib/semian/activerecord_trilogy_adapter.rb +5 -4
- data/lib/semian/adapter.rb +15 -5
- data/lib/semian/net_http.rb +27 -5
- data/lib/semian/redis/v5.rb +8 -0
- data/lib/semian/redis.rb +8 -0
- data/lib/semian/redis_client.rb +8 -0
- data/lib/semian/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 47d9f8254a2231b2ca3210ff09331ace5bd23f86bfe0f178a95e64187d3f1203
|
4
|
+
data.tar.gz: 644bcb5a30040499af851b17a9cb46c7a2a4fe0a3896cfd7052bc39e115fb4bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3d5769728bd70009178ad78d1b397b5a067e2482578e0916ae76963870cc9808499a49cd5a22f6a3ffa254cbb203f5dec37584352815cb9ba32a8a281432468
|
7
|
+
data.tar.gz: 7ea88141334bed5ecf490f25bd3b8d98759de50f38824af6f0af70619f0688eeacea05c80181a079fb3563d887fc1d1dbc6840893eb4a8c78e729a1c4b498b94
|
data/README.md
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
## Semian ![Build Status](https://github.com/Shopify/semian/actions/workflows/
|
2
|
-
|
1
|
+
## Semian ![Build Status](https://github.com/Shopify/semian/actions/workflows/test.yml/badge.svg)
|
3
2
|
|
4
3
|
![](http://i.imgur.com/7Vn2ibF.png)
|
5
4
|
|
@@ -255,10 +254,40 @@ The `semian_options` passed apply to that resource. Semian creates the `semian_i
|
|
255
254
|
from the `name` to look up and store changes in the circuit breaker and bulkhead states
|
256
255
|
and associate successes, failures, errors with the protected resource.
|
257
256
|
|
258
|
-
We only require that
|
259
|
-
|
260
|
-
|
261
|
-
|
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
|
+
```
|
262
291
|
|
263
292
|
For most purposes, `"#{host}_#{port}"` is a good default `name`. Custom `name` formats
|
264
293
|
can be useful to grouping related subdomains as one resource, so that they all
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "semian/adapter"
|
4
|
-
require "
|
4
|
+
require "active_record"
|
5
5
|
require "active_record/connection_adapters/trilogy_adapter"
|
6
6
|
|
7
7
|
module ActiveRecord
|
@@ -47,15 +47,16 @@ module Semian
|
|
47
47
|
super
|
48
48
|
end
|
49
49
|
|
50
|
-
def execute(sql,
|
50
|
+
def execute(sql, *)
|
51
51
|
if query_allowlisted?(sql)
|
52
|
-
super
|
52
|
+
super
|
53
53
|
else
|
54
54
|
acquire_semian_resource(adapter: :trilogy_adapter, scope: :execute) do
|
55
|
-
super
|
55
|
+
super
|
56
56
|
end
|
57
57
|
end
|
58
58
|
end
|
59
|
+
ruby2_keywords :execute
|
59
60
|
|
60
61
|
def active?
|
61
62
|
acquire_semian_resource(adapter: :trilogy_adapter, scope: :ping) do
|
data/lib/semian/adapter.rb
CHANGED
@@ -9,19 +9,25 @@ module Semian
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def semian_resource
|
12
|
-
@semian_resource
|
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
|
-
|
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
|
data/lib/semian/net_http.rb
CHANGED
@@ -91,16 +91,20 @@ module Semian
|
|
91
91
|
end
|
92
92
|
|
93
93
|
def connect
|
94
|
-
|
94
|
+
with_cleared_dynamic_options do
|
95
|
+
return super if disabled?
|
95
96
|
|
96
|
-
|
97
|
+
acquire_semian_resource(adapter: :http, scope: :connection) { super }
|
98
|
+
end
|
97
99
|
end
|
98
100
|
|
99
101
|
def transport_request(*)
|
100
|
-
|
102
|
+
with_cleared_dynamic_options do
|
103
|
+
return super if disabled?
|
101
104
|
|
102
|
-
|
103
|
-
|
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
|
|
data/lib/semian/redis/v5.rb
CHANGED
@@ -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)
|
data/lib/semian/redis_client.rb
CHANGED
@@ -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)
|
data/lib/semian/version.rb
CHANGED
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.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-
|
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
|
@@ -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.
|
82
|
+
rubygems_version: 3.4.12
|
83
83
|
signing_key:
|
84
84
|
specification_version: 4
|
85
85
|
summary: Bulkheading for Ruby with SysV semaphores
|