network_resiliency 0.5.1 → 0.6.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: 6bf447ec73846d8911df202dc4a2ba576879a2768cd2a2e817ae9119a0e40506
4
- data.tar.gz: d38481d92461272cbcefd34ea81f2c82d38381e135a06daed527210cdce6a178
3
+ metadata.gz: a94a624ababefe0045c52a57be145826d8072f8367d494afdef4892eb81af066
4
+ data.tar.gz: dc1412ead0b212c14d1707236cb76b5d19935ab6d9562cee01ef6a761c70bcb5
5
5
  SHA512:
6
- metadata.gz: 73b6e3fa940f4f8b91e26cc039b2bdfb37f7879a2a4c60ac1cc29e0572e79fd427c4984dfd8a139695e1225a370e295330833d81e250c6b3f922f467fb5f0b00
7
- data.tar.gz: 00a8fe95736393d1e9133ca4d2d0c9873d859a93c53ef487503ad00762eadcaba18bad6c99138910a9bbf4916cc7eae5a464d8324657321b68eaaca076a5a10f
6
+ metadata.gz: 2db8772835afb0358179255e94960499fbfa4bcebcd6bba532e38a0a533b658e9dfd2a94a12de5d7ef29aef372d671e14d04a226b19acddc1621d80617056d7a
7
+ data.tar.gz: b9aa489ff41eecde7aa9bee4e0b8377a8952827dff9a0911272d987bd487fb4b5bdcde02974414bc6ad25b7c6490585282baa4dd189e66a76d5b549743af088f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### v0.6.1 (2023-11-21)
2
+ - bugfix
3
+ - redis system errors
4
+
5
+ ### v0.6.0 (2023-11-21)
6
+ - http resiliency
7
+ - redis resilience
8
+ - spec cleanup
9
+
1
10
  ### v0.5.1 (2023-11-17)
2
11
  - support nil timeouts
3
12
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- network_resiliency (0.5.1)
4
+ network_resiliency (0.6.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -15,19 +15,44 @@ module NetworkResiliency
15
15
  (instance&.singleton_class || Net::HTTP).ancestors.include?(Instrumentation)
16
16
  end
17
17
 
18
- module Instrumentation
19
- def connect
20
- return super unless NetworkResiliency.enabled?(:http)
21
- original_timeout = self.open_timeout
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: "connect",
26
- destination: address,
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
+
53
+ original_max_retries = self.max_retries
54
+ self.max_retries = 0 # disable
55
+
31
56
  attempts = 0
32
57
  ts = -NetworkResiliency.timestamp
33
58
 
@@ -35,10 +60,13 @@ module NetworkResiliency
35
60
  attempts += 1
36
61
  error = nil
37
62
 
38
- self.open_timeout = timeouts.shift
63
+ set_timeout.call(timeouts.shift)
64
+
65
+ yield
66
+ rescue ::Timeout::Error,
67
+ defined?(OpenSSL::SSL) ? OpenSSL::OpenSSLError : IOError,
68
+ SystemCallError => e
39
69
 
40
- super
41
- rescue Net::OpenTimeout => e
42
70
  # capture error
43
71
  error = e.class
44
72
 
@@ -47,20 +75,45 @@ module NetworkResiliency
47
75
  raise
48
76
  ensure
49
77
  ts += NetworkResiliency.timestamp
50
- self.open_timeout = original_timeout
78
+ set_timeout.call(original_timeout)
79
+ self.max_retries = original_max_retries
51
80
 
52
81
  NetworkResiliency.record(
53
82
  adapter: "http",
54
- action: "connect",
55
- destination: address,
56
- error: error,
83
+ action: action.to_s,
84
+ destination: destination,
57
85
  duration: ts,
58
- timeout: self.open_timeout.to_f * 1_000,
86
+ error: error,
87
+ timeout: original_timeout.to_f * 1_000,
59
88
  attempts: attempts,
60
89
  )
61
90
  end
62
91
  end
63
92
  end
93
+
94
+ module Instrumentation
95
+ using NetworkResiliency::Adapter::HTTP
96
+
97
+ def connect
98
+ return super unless NetworkResiliency.enabled?(:http)
99
+
100
+ with_resilience(:connect, address, true) { super }
101
+ end
102
+
103
+ def transport_request(req, &block)
104
+ return super unless NetworkResiliency.enabled?(:http)
105
+
106
+ destination = [
107
+ req.method.downcase,
108
+ address,
109
+ normalize_path(req.path),
110
+ ].join(":")
111
+
112
+ idepotent = Net::HTTP::IDEMPOTENT_METHODS_.include?(req.method)
113
+
114
+ with_resilience(:request, destination, idepotent) { super }
115
+ end
116
+ end
64
117
  end
65
118
  end
66
119
  end
@@ -35,20 +35,44 @@ module NetworkResiliency
35
35
  end
36
36
  end
37
37
 
38
- module Instrumentation
39
- def establish_connection
40
- return super unless NetworkResiliency.enabled?(:redis)
41
-
42
- original_timeout = @options[:connect_timeout]
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: "connect",
47
- destination: host,
48
- max: original_timeout,
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,33 +80,71 @@ module NetworkResiliency
56
80
  attempts += 1
57
81
  error = nil
58
82
 
59
- @options[:connect_timeout] = timeouts.shift
83
+ @options[timeout_key] = timeouts.shift
60
84
 
61
- super
62
- rescue ::Redis::CannotConnectError => e
85
+ yield
86
+ rescue ::Redis::BaseConnectionError, SystemCallError => e
63
87
  # capture error
64
88
 
65
89
  # grab underlying exception within Redis wrapper
66
- error = e.cause.class
90
+ error = if e.is_a?(::Redis::BaseConnectionError)
91
+ e.cause.class
92
+ else
93
+ e.class
94
+ end
67
95
 
68
96
  retry if timeouts.size > 0
69
97
 
70
98
  raise
71
99
  ensure
72
100
  ts += NetworkResiliency.timestamp
73
- @options[:connect_timeout] = original_timeout
101
+ @options[timeout_key] = original_timeout
74
102
 
75
103
  NetworkResiliency.record(
76
104
  adapter: "redis",
77
- action: "connect",
78
- destination: host,
105
+ action: action.to_s,
106
+ destination: destination,
79
107
  duration: ts,
80
108
  error: error,
81
- timeout: @options[:connect_timeout].to_f * 1_000,
109
+ timeout: @options[timeout_key].to_f * 1_000,
82
110
  attempts: attempts,
83
111
  )
84
112
  end
85
113
  end
114
+
115
+ def idempotent?(command)
116
+ NetworkResiliency::Adapter::Redis::IDEMPOTENT_COMMANDS.include?(command)
117
+ end
118
+ end
119
+
120
+ module Instrumentation
121
+ using NetworkResiliency::Refinements
122
+ using NetworkResiliency::Adapter::Redis
123
+
124
+ def establish_connection
125
+ return super unless NetworkResiliency.enabled?(:redis)
126
+
127
+ with_resilience(:connect, host, true) { super }
128
+ end
129
+
130
+ def call(command)
131
+ return super unless NetworkResiliency.enabled?(:redis)
132
+ return super unless command.is_a?(Array)
133
+
134
+ command_key = command.first.to_s
135
+
136
+ # larger commands may have larger timeouts
137
+ command_size = command.size.order_of_magnitude
138
+ destination = [
139
+ host,
140
+ command_key,
141
+ (command_size if command_size > 1),
142
+ ].compact.join(":")
143
+
144
+ idempotent = idempotent?(command_key)
145
+
146
+ with_resilience(:request, destination, idempotent) { super }
147
+ end
86
148
  end
87
149
  end
88
150
  end
@@ -1,3 +1,3 @@
1
1
  module NetworkResiliency
2
- VERSION = "0.5.1"
2
+ VERSION = "0.6.1"
3
3
  end
@@ -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.5.1
4
+ version: 0.6.1
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-17 00:00:00.000000000 Z
11
+ date: 2023-11-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: byebug