httpx 1.6.3 → 1.7.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/doc/release_notes/0_11_0.md +3 -3
  3. data/doc/release_notes/1_6_3.md +2 -2
  4. data/doc/release_notes/1_7_0.md +149 -0
  5. data/lib/httpx/adapters/datadog.rb +1 -1
  6. data/lib/httpx/adapters/faraday.rb +1 -1
  7. data/lib/httpx/altsvc.rb +3 -1
  8. data/lib/httpx/connection/http1.rb +5 -6
  9. data/lib/httpx/connection/http2.rb +2 -0
  10. data/lib/httpx/connection.rb +3 -8
  11. data/lib/httpx/domain_name.rb +1 -1
  12. data/lib/httpx/headers.rb +2 -2
  13. data/lib/httpx/loggable.rb +2 -0
  14. data/lib/httpx/options.rb +40 -17
  15. data/lib/httpx/plugins/auth/digest.rb +44 -4
  16. data/lib/httpx/plugins/auth.rb +87 -4
  17. data/lib/httpx/plugins/aws_sdk_authentication.rb +0 -1
  18. data/lib/httpx/plugins/cookies/cookie.rb +1 -0
  19. data/lib/httpx/plugins/digest_auth.rb +4 -5
  20. data/lib/httpx/plugins/fiber_concurrency.rb +16 -1
  21. data/lib/httpx/plugins/grpc/grpc_encoding.rb +1 -1
  22. data/lib/httpx/plugins/grpc.rb +2 -2
  23. data/lib/httpx/plugins/internal_telemetry.rb +1 -1
  24. data/lib/httpx/plugins/ntlm_auth.rb +5 -3
  25. data/lib/httpx/plugins/oauth.rb +162 -56
  26. data/lib/httpx/plugins/rate_limiter.rb +2 -2
  27. data/lib/httpx/plugins/response_cache.rb +3 -7
  28. data/lib/httpx/plugins/retries.rb +55 -16
  29. data/lib/httpx/plugins/ssrf_filter.rb +1 -1
  30. data/lib/httpx/plugins/stream.rb +59 -8
  31. data/lib/httpx/plugins/stream_bidi.rb +73 -17
  32. data/lib/httpx/pool.rb +12 -2
  33. data/lib/httpx/request.rb +10 -1
  34. data/lib/httpx/resolver/https.rb +67 -17
  35. data/lib/httpx/resolver/multi.rb +4 -0
  36. data/lib/httpx/resolver/native.rb +26 -4
  37. data/lib/httpx/resolver/resolver.rb +2 -2
  38. data/lib/httpx/resolver.rb +97 -29
  39. data/lib/httpx/response/body.rb +2 -0
  40. data/lib/httpx/response.rb +22 -6
  41. data/lib/httpx/selector.rb +9 -0
  42. data/lib/httpx/session.rb +6 -6
  43. data/lib/httpx/transcoder/body.rb +1 -1
  44. data/lib/httpx/transcoder/json.rb +1 -1
  45. data/lib/httpx/transcoder/multipart/decoder.rb +4 -4
  46. data/lib/httpx/transcoder/multipart/encoder.rb +1 -1
  47. data/lib/httpx/transcoder/multipart.rb +16 -8
  48. data/lib/httpx/transcoder.rb +4 -6
  49. data/lib/httpx/version.rb +1 -1
  50. data/sig/altsvc.rbs +3 -0
  51. data/sig/chainable.rbs +3 -3
  52. data/sig/connection.rbs +1 -3
  53. data/sig/options.rbs +1 -1
  54. data/sig/plugins/auth/digest.rbs +6 -0
  55. data/sig/plugins/auth.rbs +28 -4
  56. data/sig/plugins/basic_auth.rbs +3 -3
  57. data/sig/plugins/digest_auth.rbs +2 -4
  58. data/sig/plugins/fiber_concurrency.rbs +6 -0
  59. data/sig/plugins/ntlm_auth.rbs +2 -2
  60. data/sig/plugins/oauth.rbs +46 -15
  61. data/sig/plugins/rate_limiter.rbs +1 -1
  62. data/sig/plugins/response_cache/file_store.rbs +2 -0
  63. data/sig/plugins/response_cache.rbs +4 -0
  64. data/sig/plugins/retries.rbs +8 -2
  65. data/sig/plugins/stream.rbs +13 -3
  66. data/sig/plugins/stream_bidi.rbs +2 -2
  67. data/sig/pool.rbs +1 -1
  68. data/sig/resolver/https.rbs +5 -0
  69. data/sig/resolver/multi.rbs +2 -0
  70. data/sig/resolver/native.rbs +2 -0
  71. data/sig/resolver.rbs +12 -3
  72. data/sig/response.rbs +3 -0
  73. data/sig/session.rbs +3 -5
  74. data/sig/transcoder/multipart.rbs +4 -2
  75. data/sig/transcoder.rbs +5 -1
  76. metadata +3 -1
@@ -26,6 +26,8 @@ module HTTPX
26
26
  class_eval(<<-METH, __FILE__, __LINE__ + 1)
27
27
  # lock.aware version of +#{lock_meth}+
28
28
  def #{lock_meth}(*) # def close(*)
29
+ return super unless @options.stream
30
+
29
31
  return super if @lock.owned?
30
32
 
31
33
  # small race condition between
@@ -43,6 +45,8 @@ module HTTPX
43
45
  class_eval(<<-METH, __FILE__, __LINE__ + 1)
44
46
  # lock.aware version of +#{lock_meth}+
45
47
  private def #{lock_meth}(*) # private def join_headers(*)
48
+ return super unless @options.stream
49
+
46
50
  return super if @lock.owned?
47
51
 
48
52
  # small race condition between
@@ -55,6 +59,8 @@ module HTTPX
55
59
  end
56
60
 
57
61
  def handle_stream(stream, request)
62
+ return super unless @options.stream
63
+
58
64
  request.on(:body) do
59
65
  next unless request.headers_sent
60
66
 
@@ -67,6 +73,8 @@ module HTTPX
67
73
 
68
74
  # when there ain't more chunks, it makes the buffer as full.
69
75
  def send_chunk(request, stream, chunk, next_chunk)
76
+ return super unless @options.stream
77
+
70
78
  super
71
79
 
72
80
  return if next_chunk
@@ -77,39 +85,60 @@ module HTTPX
77
85
 
78
86
  # sets end-stream flag when the request is closed.
79
87
  def end_stream?(request, next_chunk)
88
+ return super unless @options.stream
89
+
80
90
  request.closed? && next_chunk.nil?
81
91
  end
82
92
  end
83
93
 
84
- # BidiBuffer is a Buffer which can be receive data from threads othr
85
- # than the thread of the corresponding Connection/Session.
94
+ # BidiBuffer is a thread-safe Buffer which can receive data from any thread.
95
+ #
96
+ # It uses a dual-buffer strategy with mutex protection:
97
+ # - +@buffer+ is the main buffer, protected by +@buffer_mutex+
98
+ # - +@oob_buffer+ receives data when +@buffer_mutex+ is contended
86
99
  #
87
- # It synchronizes access to a secondary internal +@oob_buffer+, which periodically
88
- # is reconciled to the main internal +@buffer+.
100
+ # This allows non-blocking writes from any thread while maintaining thread safety.
89
101
  class BidiBuffer < Buffer
90
102
  def initialize(*)
91
103
  super
92
- @parent_thread = Thread.current
104
+ @buffer_mutex = Thread::Mutex.new
93
105
  @oob_mutex = Thread::Mutex.new
94
106
  @oob_buffer = "".b
95
107
  end
96
108
 
97
- # buffers the +chunk+ to be sent
109
+ # buffers the +chunk+ to be sent (thread-safe, non-blocking)
98
110
  def <<(chunk)
99
- return super if Thread.current == @parent_thread
100
-
101
- @oob_mutex.synchronize { @oob_buffer << chunk }
111
+ if @buffer_mutex.try_lock
112
+ begin
113
+ super
114
+ ensure
115
+ @buffer_mutex.unlock
116
+ end
117
+ else
118
+ # another thread holds the lock, use OOB buffer to avoid blocking
119
+ @oob_mutex.synchronize { @oob_buffer << chunk }
120
+ end
102
121
  end
103
122
 
104
- # reconciles the main and secondary buffer (which receives data from other threads).
123
+ # reconciles the main and secondary buffer (thread-safe, callable from any thread).
105
124
  def rebuffer
106
- raise Error, "can only rebuffer while waiting on a response" unless Thread.current == @parent_thread
125
+ @buffer_mutex.synchronize do
126
+ @oob_mutex.synchronize do
127
+ return if @oob_buffer.empty?
107
128
 
108
- @oob_mutex.synchronize do
109
- @buffer << @oob_buffer
110
- @oob_buffer.clear
129
+ @buffer << @oob_buffer
130
+ @oob_buffer.clear
131
+ end
111
132
  end
112
133
  end
134
+
135
+ Buffer.instance_methods - Object.instance_methods - %i[<<].each do |meth|
136
+ class_eval(<<-MOD, __FILE__, __LINE__ + 1)
137
+ def #{meth}(*) # def empty?
138
+ @buffer_mutex.synchronize { super }
139
+ end
140
+ MOD
141
+ end
113
142
  end
114
143
 
115
144
  # Proxy to wake up the session main loop when one
@@ -156,7 +185,13 @@ module HTTPX
156
185
 
157
186
  def timeout; end
158
187
 
188
+ def inflight?
189
+ !@closed
190
+ end
191
+
159
192
  def terminate
193
+ return if @closed
194
+
160
195
  @pipe_write.close
161
196
  @pipe_read.close
162
197
  @closed = true
@@ -190,16 +225,20 @@ module HTTPX
190
225
  def close(selector = Selector.new)
191
226
  @signal.terminate
192
227
  selector.deregister(@signal)
193
- super(selector)
228
+ super
194
229
  end
195
230
 
196
231
  def select_connection(connection, selector)
232
+ return super unless connection.options.stream
233
+
197
234
  super
198
235
  selector.register(@signal)
199
236
  connection.signal = @signal
200
237
  end
201
238
 
202
239
  def deselect_connection(connection, *)
240
+ return super unless connection.options.stream
241
+
203
242
  super
204
243
 
205
244
  connection.signal = nil
@@ -219,10 +258,14 @@ module HTTPX
219
258
  end
220
259
 
221
260
  def closed?
261
+ return super unless @options.stream
262
+
222
263
  @closed
223
264
  end
224
265
 
225
266
  def can_buffer?
267
+ return super unless @options.stream
268
+
226
269
  super && @state != :waiting_for_chunk
227
270
  end
228
271
 
@@ -230,6 +273,8 @@ module HTTPX
230
273
  # +:waiting_for_chunk+ state, which the request transitions to once payload
231
274
  # is buffered.
232
275
  def transition(nextstate)
276
+ return super unless @options.stream
277
+
233
278
  headers_sent = @headers_sent
234
279
 
235
280
  case nextstate
@@ -264,6 +309,8 @@ module HTTPX
264
309
  end
265
310
 
266
311
  def close
312
+ return super unless @options.stream
313
+
267
314
  @mutex.synchronize do
268
315
  return if @closed
269
316
 
@@ -278,10 +325,12 @@ module HTTPX
278
325
  module RequestBodyMethods
279
326
  def initialize(*, **)
280
327
  super
281
- @headers.delete("content-length")
328
+ @headers.delete("content-length") if @options.stream
282
329
  end
283
330
 
284
331
  def empty?
332
+ return super unless @options.stream
333
+
285
334
  false
286
335
  end
287
336
  end
@@ -293,18 +342,23 @@ module HTTPX
293
342
 
294
343
  def initialize(*)
295
344
  super
345
+
346
+ return unless @options.stream
347
+
296
348
  @write_buffer = BidiBuffer.new(@options.buffer_size)
297
349
  end
298
350
 
299
351
  # rebuffers the +@write_buffer+ before calculating interests.
300
352
  def interests
353
+ return super unless @options.stream
354
+
301
355
  @write_buffer.rebuffer
302
356
 
303
357
  super
304
358
  end
305
359
 
306
360
  def call
307
- return super unless (error = @signal.error)
361
+ return super unless @options.stream && (error = @signal.error)
308
362
 
309
363
  on_error(error)
310
364
  end
@@ -312,6 +366,8 @@ module HTTPX
312
366
  private
313
367
 
314
368
  def set_parser_callbacks(parser)
369
+ return super unless @options.stream
370
+
315
371
  super
316
372
  parser.on(:flush_buffer) do
317
373
  @signal.wakeup if @signal
data/lib/httpx/pool.rb CHANGED
@@ -122,6 +122,12 @@ module HTTPX
122
122
 
123
123
  @max_connections_cond.signal
124
124
  @origin_conds[connection.origin.to_s].signal
125
+
126
+ # Observed situations where a session handling multiple requests in a loop
127
+ # across multiple threads checks the same connection in and out, while another
128
+ # thread which is waiting on the same connection never gets the chance to pick
129
+ # it up, because ruby's thread scheduler never switched on to it in the process.
130
+ Thread.pass
125
131
  end
126
132
  end
127
133
 
@@ -193,15 +199,19 @@ module HTTPX
193
199
  end
194
200
 
195
201
  def checkout_new_connection(uri, options)
196
- options.connection_class.new(uri, options)
202
+ connection = options.connection_class.new(uri, options)
203
+ connection.log(level: 2) { "created connection##{connection.object_id} in pool##{object_id}" }
204
+ connection
197
205
  end
198
206
 
199
207
  def checkout_new_resolver(resolver_type, options)
200
- if resolver_type.multi?
208
+ resolver = if resolver_type.multi?
201
209
  Resolver::Multi.new(resolver_type, options)
202
210
  else
203
211
  resolver_type.new(options)
204
212
  end
213
+ resolver.log(level: 2) { "created resolver##{resolver.object_id} in pool##{object_id}" }
214
+ resolver
205
215
  end
206
216
 
207
217
  # drops and returns the +connection+ from the connection pool; if +connection+ is <tt>nil</tt> (default),
data/lib/httpx/request.rb CHANGED
@@ -10,6 +10,7 @@ module HTTPX
10
10
  extend Forwardable
11
11
  include Loggable
12
12
  include Callbacks
13
+
13
14
  using URIExtensions
14
15
 
15
16
  ALLOWED_URI_SCHEMES = %w[https http].freeze
@@ -90,12 +91,20 @@ module HTTPX
90
91
  raise UnsupportedSchemeError, "#{@uri}: #{@uri.scheme}: unsupported URI scheme" unless ALLOWED_URI_SCHEMES.include?(@uri.scheme)
91
92
 
92
93
  @state = :idle
93
- @response = @peer_address = @context = @informational_status = nil
94
+ @response = @peer_address = @informational_status = nil
94
95
  @ping = false
95
96
  @persistent = @options.persistent
96
97
  @active_timeouts = []
97
98
  end
98
99
 
100
+ # dupped initialization
101
+ def initialize_dup(orig)
102
+ super
103
+ @uri = orig.instance_variable_get(:@uri).dup
104
+ @headers = orig.instance_variable_get(:@headers).dup
105
+ @body = orig.instance_variable_get(:@body).dup
106
+ end
107
+
99
108
  def complete!(response = @response)
100
109
  emit(:complete, response)
101
110
  end
@@ -12,6 +12,7 @@ module HTTPX
12
12
  #
13
13
  class Resolver::HTTPS < Resolver::Resolver
14
14
  extend Forwardable
15
+
15
16
  using URIExtensions
16
17
 
17
18
  module DNSExtensions
@@ -38,10 +39,12 @@ module HTTPX
38
39
  @resolver_options = DEFAULTS.merge(@options.resolver_options)
39
40
  @queries = {}
40
41
  @requests = {}
42
+ @_timeouts = Array(@resolver_options[:timeouts])
43
+ @timeouts = Hash.new { |timeouts, host| timeouts[host] = @_timeouts.dup }
41
44
  @uri = URI(@resolver_options[:uri])
42
- @uri_addresses = nil
45
+ @name = @uri_addresses = nil
43
46
  @resolver = Resolv::DNS.new
44
- @resolver.timeouts = @resolver_options.fetch(:timeouts, Resolver::RESOLVE_TIMEOUT)
47
+ @resolver.timeouts = @_timeouts.empty? ? Resolver::RESOLVE_TIMEOUT : @_timeouts
45
48
  @resolver.lazy_initialize
46
49
  end
47
50
 
@@ -96,21 +99,26 @@ module HTTPX
96
99
  else
97
100
  @queries[hostname] = connection
98
101
  end
102
+
103
+ @name = hostname
104
+
99
105
  log { "resolver #{FAMILY_TYPES[@record_type]}: query for #{hostname}" }
100
106
 
101
- begin
102
- request = build_request(hostname)
103
- request.on(:response, &method(:on_response).curry(2)[request])
104
- request.on(:promise, &method(:on_promise))
105
- @requests[request] = hostname
106
- resolver_connection.send(request)
107
- @connections << connection
108
- rescue ResolveError, Resolv::DNS::EncodeError => e
109
- reset_hostname(hostname)
110
- throw(:resolve_error, e) if connection.pending.empty?
111
- emit_resolve_error(connection, connection.peer.host, e)
112
- close_or_resolve
113
- end
107
+ send_request(hostname, connection)
108
+ end
109
+
110
+ def send_request(hostname, connection)
111
+ request = build_request(hostname)
112
+ request.on(:response, &method(:on_response).curry(2)[request])
113
+ request.on(:promise, &method(:on_promise))
114
+ @requests[request] = hostname
115
+ resolver_connection.send(request)
116
+ @connections << connection
117
+ rescue ResolveError, Resolv::DNS::EncodeError => e
118
+ reset_hostname(hostname)
119
+ throw(:resolve_error, e) if connection.pending.empty?
120
+ emit_resolve_error(connection, connection.peer.host, e)
121
+ close_or_resolve
114
122
  end
115
123
 
116
124
  def on_response(request, response)
@@ -122,6 +130,18 @@ module HTTPX
122
130
  close_or_resolve
123
131
  else
124
132
  # @type var response: HTTPX::Response
133
+ if response.status.between?(300, 399) && response.headers.key?("location")
134
+ hostname = @requests[request]
135
+ connection = @queries[hostname]
136
+ location_uri = URI(response.headers["location"])
137
+ location_uri = response.uri.merge(location_uri) if location_uri.relative?
138
+
139
+ # we assume that the DNS server URI changed permanently and move on
140
+ @uri = location_uri
141
+ send_request(hostname, connection)
142
+ return
143
+ end
144
+
125
145
  parse(request, response)
126
146
  ensure
127
147
  @requests.delete(request)
@@ -133,6 +153,10 @@ module HTTPX
133
153
  end
134
154
 
135
155
  def parse(request, response)
156
+ hostname = @name
157
+
158
+ @name = nil
159
+
136
160
  code, result = decode_response_body(response)
137
161
 
138
162
  case code
@@ -151,6 +175,23 @@ module HTTPX
151
175
  end
152
176
 
153
177
  resolve
178
+ when :retriable_error
179
+ timeouts = @timeouts[hostname]
180
+
181
+ unless timeouts.empty?
182
+ log { "resolver #{FAMILY_TYPES[@record_type]}: failed, but will retry..." }
183
+
184
+ connection = @queries[hostname]
185
+
186
+ resolve(connection, hostname)
187
+ return
188
+ end
189
+
190
+ host = @requests.delete(request)
191
+ connection = reset_hostname(host)
192
+
193
+ emit_resolve_error(connection)
194
+ close_or_resolve
154
195
  when :dns_error
155
196
  host = @requests.delete(request)
156
197
  connection = reset_hostname(host)
@@ -217,15 +258,18 @@ module HTTPX
217
258
  uri = @uri.dup
218
259
  rklass = @options.request_class
219
260
  payload = Resolver.encode_dns_query(hostname, type: @record_type)
261
+ timeouts = @timeouts[hostname]
262
+ request_timeout = timeouts.shift
263
+ options = @options.merge(timeout: { request_timeout: request_timeout })
220
264
 
221
265
  if @resolver_options[:use_get]
222
266
  params = URI.decode_www_form(uri.query.to_s)
223
267
  params << ["type", FAMILY_TYPES[@record_type]]
224
268
  params << ["dns", Base64.urlsafe_encode64(payload, padding: false)]
225
269
  uri.query = URI.encode_www_form(params)
226
- request = rklass.new("GET", uri, @options)
270
+ request = rklass.new("GET", uri, options)
227
271
  else
228
- request = rklass.new("POST", uri, @options, body: [payload])
272
+ request = rklass.new("POST", uri, options, body: [payload])
229
273
  request.headers["content-type"] = "application/dns-message"
230
274
  end
231
275
  request.headers["accept"] = "application/dns-message"
@@ -243,6 +287,7 @@ module HTTPX
243
287
  end
244
288
 
245
289
  def reset_hostname(hostname, reset_candidates: true)
290
+ @timeouts.delete(hostname)
246
291
  connection = @queries.delete(hostname)
247
292
 
248
293
  return connection unless connection && reset_candidates
@@ -250,6 +295,8 @@ module HTTPX
250
295
  # eliminate other candidates
251
296
  candidates = @queries.select { |_, conn| connection == conn }.keys
252
297
  @queries.delete_if { |h, _| candidates.include?(h) }
298
+ # reset timeouts
299
+ @timeouts.delete_if { |h, _| candidates.include?(h) }
253
300
 
254
301
  connection
255
302
  end
@@ -259,6 +306,9 @@ module HTTPX
259
306
  @connections.shift until @connections.empty? || @connections.first.state != :closed
260
307
 
261
308
  if (@connections - @queries.values).empty?
309
+ # the same resolver connection may be serving different https resolvers (AAAA and A).
310
+ return if inflight?
311
+
262
312
  if should_deactivate
263
313
  deactivate
264
314
  else
@@ -23,6 +23,10 @@ module HTTPX
23
23
  @errors = Hash.new { |hs, k| hs[k] = [] }
24
24
  end
25
25
 
26
+ def state
27
+ @resolvers.map(&:state).uniq.join(",")
28
+ end
29
+
26
30
  def current_selector=(s)
27
31
  @current_selector = s
28
32
  @resolvers.each { |r| r.current_selector = s }
@@ -9,6 +9,7 @@ module HTTPX
9
9
  #
10
10
  class Resolver::Native < Resolver::Resolver
11
11
  extend Forwardable
12
+
12
13
  using URIExtensions
13
14
 
14
15
  DEFAULTS = {
@@ -298,16 +299,14 @@ module HTTPX
298
299
  end
299
300
 
300
301
  def parse(buffer)
301
- @timer.cancel
302
-
303
- @timer = @name = nil
304
-
305
302
  code, result = Resolver.decode_dns_answer(buffer)
306
303
 
307
304
  case code
308
305
  when :ok
306
+ reset_query
309
307
  parse_addresses(result)
310
308
  when :no_domain_found
309
+ reset_query
311
310
  # Indicates no such domain was found.
312
311
  hostname, connection = @queries.first
313
312
  reset_hostname(hostname, reset_candidates: false)
@@ -324,6 +323,7 @@ module HTTPX
324
323
  close_or_resolve
325
324
  end
326
325
  when :message_truncated
326
+ reset_query
327
327
  # TODO: what to do if it's already tcp??
328
328
  return if @socket_type == :tcp
329
329
 
@@ -332,13 +332,29 @@ module HTTPX
332
332
  hostname, _ = @queries.first
333
333
  reset_hostname(hostname)
334
334
  transition(:closed)
335
+ when :retriable_error
336
+ if @name && @timer
337
+ log { "resolver #{FAMILY_TYPES[@record_type]}: failed, but will retry..." }
338
+ return
339
+ end
340
+ # retry now!
341
+ # connection = @queries[@name].shift
342
+ # @timer.fire
343
+ reset_query
344
+ hostname, connection = @queries.first
345
+ reset_hostname(hostname)
346
+ @connections.delete(connection)
347
+ ex = NativeResolveError.new(connection, connection.peer.host, "unknown DNS error (error code #{result})")
348
+ raise ex
335
349
  when :dns_error
350
+ reset_query
336
351
  hostname, connection = @queries.first
337
352
  reset_hostname(hostname)
338
353
  @connections.delete(connection)
339
354
  ex = NativeResolveError.new(connection, connection.peer.host, "unknown DNS error (error code #{result})")
340
355
  raise ex
341
356
  when :decode_error
357
+ reset_query
342
358
  hostname, connection = @queries.first
343
359
  reset_hostname(hostname)
344
360
  @connections.delete(connection)
@@ -529,6 +545,12 @@ module HTTPX
529
545
  on_error(e)
530
546
  end
531
547
 
548
+ def reset_query
549
+ @timer.cancel
550
+
551
+ @timer = @name = nil
552
+ end
553
+
532
554
  def reset_hostname(hostname, connection: @queries.delete(hostname), reset_candidates: true)
533
555
  @timeouts.delete(hostname)
534
556
 
@@ -75,7 +75,7 @@ module HTTPX
75
75
 
76
76
  # double emission check, but allow early resolution to work
77
77
  conn_addrs = connection.addresses
78
- return if !early_resolve && conn_addrs && (!conn_addrs.empty? && !addresses.intersect?(conn_addrs))
78
+ return if !early_resolve && conn_addrs && !conn_addrs.empty? && !addresses.intersect?(conn_addrs)
79
79
 
80
80
  log do
81
81
  "resolver #{FAMILY_TYPES[RECORD_TYPES[family]]}: " \
@@ -124,7 +124,7 @@ module HTTPX
124
124
  disconnect
125
125
  end
126
126
 
127
- def early_resolve(connection, hostname: connection.peer.host)
127
+ def early_resolve(connection, hostname: connection.peer.host) # rubocop:disable Naming/PredicateMethod
128
128
  addresses = @resolver_options[:cache] && (connection.addresses || HTTPX::Resolver.nolookup_resolve(hostname))
129
129
 
130
130
  return false unless addresses