network_resiliency 0.5.0 → 0.6.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/CHANGELOG.md +8 -0
- data/Gemfile.lock +1 -1
- data/lib/network_resiliency/adapter/http.rb +68 -14
- data/lib/network_resiliency/adapter/mysql.rb +1 -1
- data/lib/network_resiliency/adapter/redis.rb +73 -15
- data/lib/network_resiliency/version.rb +1 -1
- data/lib/network_resiliency.rb +3 -3
- 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: 9cd120266db1dfb8d2e5b37155534f3d5dc37a560d329352fa1a061ffd19ec97
|
4
|
+
data.tar.gz: c09fb2b6dc81e74a838e7da9ae9bc7e4d177c9ba7104a3b8e6ecb6b8668ebf97
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9caeeab2d22c4feea69b03438f8c66588179fe130ef1620935c475654a4661bbba36d82c79b4650fb3bcdead86cf0b5f3e3746513c40d16c8208b6f09ca32e5a
|
7
|
+
data.tar.gz: 05ad9ab8d5ec490cf6064cc83c54e9132b2a10f4bf6ab8ca0ab83f94d84db706622c951a49ad0de8e0ac7551ce32963fb86ca7aad5d6f47bb0c33162d3ba0e6d
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -15,19 +15,41 @@ module NetworkResiliency
|
|
15
15
|
(instance&.singleton_class || Net::HTTP).ancestors.include?(Instrumentation)
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
ID_REGEX = %r{/[0-9]+(?=/|$)}.freeze
|
19
|
+
UUID_REGEX = %r`/\h{8}-\h{4}-(\h{4})-\h{4}-\h{12}(?=/|$)`.freeze
|
20
|
+
refine Net::HTTP do
|
21
|
+
def normalize_path(path)
|
22
|
+
path.gsub(
|
23
|
+
Regexp.union(
|
24
|
+
NetworkResiliency::Adapter::HTTP::ID_REGEX,
|
25
|
+
NetworkResiliency::Adapter::HTTP::UUID_REGEX,
|
26
|
+
),
|
27
|
+
'/x',
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_resilience(action, destination, idempotent, &block)
|
32
|
+
if action == :connect
|
33
|
+
original_timeout = self.open_timeout
|
34
|
+
set_timeout = ->(timeout) { self.open_timeout = timeout }
|
35
|
+
else
|
36
|
+
original_timeout = self.read_timeout
|
37
|
+
set_timeout = ->(timeout) { self.read_timeout = timeout }
|
38
|
+
end
|
22
39
|
|
23
40
|
timeouts = NetworkResiliency.timeouts_for(
|
24
41
|
adapter: "http",
|
25
|
-
action:
|
26
|
-
destination:
|
42
|
+
action: action.to_s,
|
43
|
+
destination: destination,
|
27
44
|
max: original_timeout,
|
28
45
|
units: :seconds,
|
29
46
|
)
|
30
47
|
|
48
|
+
unless idempotent
|
49
|
+
# only try once, with most lenient timeout
|
50
|
+
timeouts = timeouts.last(1)
|
51
|
+
end
|
52
|
+
|
31
53
|
attempts = 0
|
32
54
|
ts = -NetworkResiliency.timestamp
|
33
55
|
|
@@ -35,10 +57,13 @@ module NetworkResiliency
|
|
35
57
|
attempts += 1
|
36
58
|
error = nil
|
37
59
|
|
38
|
-
|
60
|
+
set_timeout.call(timeouts.shift)
|
61
|
+
|
62
|
+
yield
|
63
|
+
rescue ::Timeout::Error,
|
64
|
+
defined?(OpenSSL::SSL) ? OpenSSL::OpenSSLError : IOError,
|
65
|
+
SystemCallError => e
|
39
66
|
|
40
|
-
super
|
41
|
-
rescue Net::OpenTimeout => e
|
42
67
|
# capture error
|
43
68
|
error = e.class
|
44
69
|
|
@@ -47,20 +72,49 @@ module NetworkResiliency
|
|
47
72
|
raise
|
48
73
|
ensure
|
49
74
|
ts += NetworkResiliency.timestamp
|
50
|
-
|
75
|
+
set_timeout.call(original_timeout)
|
51
76
|
|
52
77
|
NetworkResiliency.record(
|
53
78
|
adapter: "http",
|
54
|
-
action:
|
55
|
-
destination:
|
56
|
-
error: error,
|
79
|
+
action: action.to_s,
|
80
|
+
destination: destination,
|
57
81
|
duration: ts,
|
58
|
-
|
82
|
+
error: error,
|
83
|
+
timeout: original_timeout.to_f * 1_000,
|
59
84
|
attempts: attempts,
|
60
85
|
)
|
61
86
|
end
|
62
87
|
end
|
63
88
|
end
|
89
|
+
|
90
|
+
module Instrumentation
|
91
|
+
using NetworkResiliency::Adapter::HTTP
|
92
|
+
|
93
|
+
def connect
|
94
|
+
return super unless NetworkResiliency.enabled?(:http)
|
95
|
+
|
96
|
+
with_resilience(:connect, address, true) { super }
|
97
|
+
end
|
98
|
+
|
99
|
+
def transport_request(req, &block)
|
100
|
+
return super unless NetworkResiliency.enabled?(:http)
|
101
|
+
|
102
|
+
destination = [
|
103
|
+
req.method.downcase,
|
104
|
+
address,
|
105
|
+
normalize_path(req.path),
|
106
|
+
].join(":")
|
107
|
+
|
108
|
+
idepotent = Net::HTTP::IDEMPOTENT_METHODS_.include?(req.method)
|
109
|
+
|
110
|
+
retries = self.max_retries
|
111
|
+
self.max_retries = 0 # disable
|
112
|
+
|
113
|
+
with_resilience(:request, destination, idepotent) { super }
|
114
|
+
ensure
|
115
|
+
self.max_retries = retries
|
116
|
+
end
|
117
|
+
end
|
64
118
|
end
|
65
119
|
end
|
66
120
|
end
|
@@ -35,20 +35,44 @@ module NetworkResiliency
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
IDEMPOTENT_COMMANDS = [
|
39
|
+
"exists",
|
40
|
+
"expire",
|
41
|
+
"get",
|
42
|
+
"getex",
|
43
|
+
"getrange",
|
44
|
+
"mget",
|
45
|
+
"mset",
|
46
|
+
"ping",
|
47
|
+
"scard",
|
48
|
+
"sdiff",
|
49
|
+
"sdiffstore",
|
50
|
+
"set",
|
51
|
+
"sismember",
|
52
|
+
"smembers",
|
53
|
+
"smismember",
|
54
|
+
].freeze
|
55
|
+
|
56
|
+
refine ::Redis::Client do
|
57
|
+
private
|
58
|
+
|
59
|
+
def with_resilience(action, destination, idempotent, &block)
|
60
|
+
timeout_key = action == :connect ? :connect_timeout : :read_timeout
|
61
|
+
original_timeout = @options[timeout_key]
|
43
62
|
|
44
63
|
timeouts = NetworkResiliency.timeouts_for(
|
45
64
|
adapter: "redis",
|
46
|
-
action:
|
47
|
-
destination:
|
48
|
-
max:
|
65
|
+
action: action.to_s,
|
66
|
+
destination: destination,
|
67
|
+
max: @options[timeout_key],
|
49
68
|
units: :seconds,
|
50
69
|
)
|
51
70
|
|
71
|
+
unless idempotent
|
72
|
+
# only try once, with most lenient timeout
|
73
|
+
timeouts = timeouts.last(1)
|
74
|
+
end
|
75
|
+
|
52
76
|
attempts = 0
|
53
77
|
ts = -NetworkResiliency.timestamp
|
54
78
|
|
@@ -56,10 +80,10 @@ module NetworkResiliency
|
|
56
80
|
attempts += 1
|
57
81
|
error = nil
|
58
82
|
|
59
|
-
@options[
|
83
|
+
@options[timeout_key] = timeouts.shift
|
60
84
|
|
61
|
-
|
62
|
-
rescue ::Redis::
|
85
|
+
yield
|
86
|
+
rescue ::Redis::BaseConnectionError => e
|
63
87
|
# capture error
|
64
88
|
|
65
89
|
# grab underlying exception within Redis wrapper
|
@@ -70,19 +94,53 @@ module NetworkResiliency
|
|
70
94
|
raise
|
71
95
|
ensure
|
72
96
|
ts += NetworkResiliency.timestamp
|
73
|
-
@options[
|
97
|
+
@options[timeout_key] = original_timeout
|
74
98
|
|
75
99
|
NetworkResiliency.record(
|
76
100
|
adapter: "redis",
|
77
|
-
action:
|
78
|
-
destination:
|
101
|
+
action: action.to_s,
|
102
|
+
destination: destination,
|
79
103
|
duration: ts,
|
80
104
|
error: error,
|
81
|
-
timeout: @options[
|
105
|
+
timeout: @options[timeout_key].to_f * 1_000,
|
82
106
|
attempts: attempts,
|
83
107
|
)
|
84
108
|
end
|
85
109
|
end
|
110
|
+
|
111
|
+
def idempotent?(command)
|
112
|
+
NetworkResiliency::Adapter::Redis::IDEMPOTENT_COMMANDS.include?(command)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
module Instrumentation
|
117
|
+
using NetworkResiliency::Refinements
|
118
|
+
using NetworkResiliency::Adapter::Redis
|
119
|
+
|
120
|
+
def establish_connection
|
121
|
+
return super unless NetworkResiliency.enabled?(:redis)
|
122
|
+
|
123
|
+
with_resilience(:connect, host, true) { super }
|
124
|
+
end
|
125
|
+
|
126
|
+
def call(command)
|
127
|
+
return super unless NetworkResiliency.enabled?(:redis)
|
128
|
+
return super unless command.is_a?(Array)
|
129
|
+
|
130
|
+
command_key = command.first.to_s
|
131
|
+
|
132
|
+
# larger commands may have larger timeouts
|
133
|
+
command_size = command.size.order_of_magnitude
|
134
|
+
destination = [
|
135
|
+
host,
|
136
|
+
command_key,
|
137
|
+
(command_size if command_size > 1),
|
138
|
+
].compact.join(":")
|
139
|
+
|
140
|
+
idempotent = idempotent?(command_key)
|
141
|
+
|
142
|
+
with_resilience(:request, destination, idempotent) { super }
|
143
|
+
end
|
86
144
|
end
|
87
145
|
end
|
88
146
|
end
|
data/lib/network_resiliency.rb
CHANGED
@@ -135,7 +135,7 @@ module NetworkResiliency
|
|
135
135
|
adapter: adapter,
|
136
136
|
destination: destination,
|
137
137
|
},
|
138
|
-
)
|
138
|
+
) if timeout && timeout > 0
|
139
139
|
|
140
140
|
if error
|
141
141
|
NetworkResiliency.statsd&.distribution(
|
@@ -145,7 +145,7 @@ module NetworkResiliency
|
|
145
145
|
adapter: adapter,
|
146
146
|
destination: destination,
|
147
147
|
},
|
148
|
-
) if timeout
|
148
|
+
) if timeout && timeout > 0
|
149
149
|
else
|
150
150
|
# track successful retries
|
151
151
|
NetworkResiliency.statsd&.increment(
|
@@ -299,7 +299,7 @@ module NetworkResiliency
|
|
299
299
|
end
|
300
300
|
end
|
301
301
|
|
302
|
-
private
|
302
|
+
# private
|
303
303
|
|
304
304
|
def thread_state
|
305
305
|
Thread.current["network_resiliency"] ||= {}
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: network_resiliency
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Pepper
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: byebug
|