Class: HTTPX::Resolver::System
- Inherits:
-
Resolver
show all
- Defined in:
- lib/httpx/resolver/system.rb,
sig/resolver/system.rbs
Overview
Implementation of a synchronous name resolver which relies on the system resolver,
which is lib'c getaddrinfo function (abstracted in ruby via Addrinfo.getaddrinfo).
Its main advantage is relying on the reference implementation for name resolution
across most/all OSs which deploy ruby (it's what TCPSocket also uses), its main
disadvantage is the inability to set timeouts / check socket for readiness events,
hence why it relies on using the Timeout module, which poses a lot of problems for
the selector loop, specially when network is unstable.
Defined Under Namespace
Classes: AddrinfoTimeoutError
Constant Summary
collapse
- RESOLV_ERRORS =
[Resolv::ResolvError,
Resolv::DNS::Requester::RequestError,
Resolv::DNS::EncodeError,
Resolv::DNS::DecodeError].freeze
- DONE =
1
- ERROR =
2
Constants inherited
from Resolver
Resolver::FAMILY_TYPES, Resolver::RECORD_TYPES
Constants included
from Loggable
Loggable::COLORS, Loggable::USE_DEBUG_LOG
Instance Attribute Summary collapse
Attributes inherited from Resolver
#current_selector, #current_session, #family, #options
Class Method Summary
collapse
Instance Method Summary
collapse
Methods inherited from Resolver
#disconnect, #each_connection, #emit_addresses, #emit_connection_error, #emit_resolve_error, #emit_resolved_connection, #handle_error, #inflight?, #initial_call, #on_error, #on_io_error, #resolve_connection, #resolve_error
Methods included from Loggable
#log, #log_exception, log_identifiers, #log_redact, #log_redact_body, #log_redact_headers
#initial_call, #on_error, #on_io_error, #terminate
Constructor Details
#initialize(options) ⇒ System
Returns a new instance of System.
37
38
39
40
41
42
43
44
45
46
47
48
49
|
# File 'lib/httpx/resolver/system.rb', line 37
def initialize(options)
super(0, options)
@resolver_options = @options.resolver_options
resolv_options = @resolver_options.dup
timeouts = resolv_options.delete(:timeouts) || Resolver::RESOLVE_TIMEOUT
@_timeouts = Array(timeouts)
@timeouts = Hash.new { |tims, host| tims[host] = @_timeouts.dup }
resolv_options.delete(:cache)
@queries = []
@ips = []
@pipe_mutex = Thread::Mutex.new
@state = :idle
end
|
Instance Attribute Details
#state ⇒ Symbol
Returns the value of attribute state.
35
36
37
|
# File 'lib/httpx/resolver/system.rb', line 35
def state
@state
end
|
Class Method Details
.multi? ⇒ Boolean
30
31
32
|
# File 'lib/httpx/resolver/system.rb', line 30
def multi?
false
end
|
Instance Method Details
#__addrinfo_resolve(host, scheme) ⇒ Array[Addrinfo]
280
281
282
|
# File 'lib/httpx/resolver/system.rb', line 280
def __addrinfo_resolve(host, scheme)
Addrinfo.getaddrinfo(host, scheme, Socket::AF_UNSPEC, Socket::SOCK_STREAM)
end
|
#async_resolve(connection, hostname, scheme) ⇒ void
This method returns an undefined value.
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
|
# File 'lib/httpx/resolver/system.rb', line 222
def async_resolve(connection, hostname, scheme)
families = connection.options.ip_families || Resolver.supported_ip_families
log { "resolver: query for #{hostname}" }
timeouts = @timeouts[connection.peer.host]
resolve_timeout = timeouts.first
Thread.start do
Thread.current.report_on_exception = false
begin
addrs = if resolve_timeout
Timeout.timeout(resolve_timeout, AddrinfoTimeoutError) do
__addrinfo_resolve(hostname, scheme)
end
else
__addrinfo_resolve(hostname, scheme)
end
addrs = addrs.sort_by(&:afamily).group_by(&:afamily)
families.each do |family|
addresses = addrs[family]
next unless addresses
addresses.map!(&:ip_address)
addresses.uniq!
@pipe_mutex.synchronize do
@ips.unshift([family, connection, addresses])
@pipe_write.putc(DONE) unless @pipe_write.closed?
end
end
rescue StandardError => e
if e.is_a?(AddrinfoTimeoutError)
timeouts.shift
retry unless timeouts.empty?
e = ResolveTimeoutError.new(resolve_timeout, e.message)
e.set_backtrace(e.backtrace)
end
@pipe_mutex.synchronize do
families.each do |family|
@ips.unshift([family, connection, e])
@pipe_write.putc(ERROR) unless @pipe_write.closed?
end
end
end
end
Thread.pass
end
|
#call ⇒ Object
85
86
87
88
89
90
91
|
# File 'lib/httpx/resolver/system.rb', line 85
def call
case @state
when :open
consume
end
nil
end
|
#close ⇒ Object
65
66
67
|
# File 'lib/httpx/resolver/system.rb', line 65
def close
transition(:closed)
end
|
#close_or_resolve ⇒ void
This method returns an undefined value.
269
270
271
272
273
274
275
276
277
278
|
# File 'lib/httpx/resolver/system.rb', line 269
def close_or_resolve
@connections.shift until @connections.empty? || @connections.first.state != :closed
if (@connections - @queries.map(&:last)).empty?
disconnect
else
resolve
end
end
|
#closed? ⇒ Boolean
77
78
79
|
# File 'lib/httpx/resolver/system.rb', line 77
def closed?
@state == :closed
end
|
#consume ⇒ void
This method returns an undefined value.
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
# File 'lib/httpx/resolver/system.rb', line 159
def consume
return if @connections.empty?
event = @pipe_read.read_nonblock(1, exception: false)
return if event == :wait_readable
raise ResolveError, "socket pipe closed unexpectedly" if event.nil?
case event.unpack1("C")
when DONE
*pair, addrs = @pipe_mutex.synchronize { @ips.pop }
if pair
@queries.delete(pair)
family, connection = pair
@connections.delete(connection)
catch(:coalesced) { emit_addresses(connection, family, addrs) }
end
when ERROR
*pair, error = @pipe_mutex.synchronize { @ips.pop }
if pair && error
@queries.delete(pair)
_, connection = pair
@connections.delete(connection)
emit_resolve_error(connection, connection.peer.host, error)
end
end
return disconnect if @connections.empty?
resolve
rescue StandardError => e
on_error(e)
end
|
#early_resolve(_) ⇒ Object
rubocop:disable Naming/PredicateMethod
122
123
124
|
# File 'lib/httpx/resolver/system.rb', line 122
def early_resolve(_, **) false
end
|
#empty? ⇒ Boolean
61
62
63
|
# File 'lib/httpx/resolver/system.rb', line 61
def empty?
@connections.empty?
end
|
#force_close ⇒ Object
69
70
71
72
73
74
75
|
# File 'lib/httpx/resolver/system.rb', line 69
def force_close(*)
close
@queries.clear
@timeouts.clear
@ips.clear
super
end
|
#handle_socket_timeout(interval) ⇒ Object
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# File 'lib/httpx/resolver/system.rb', line 126
def handle_socket_timeout(interval)
error = HTTPX::ResolveTimeoutError.new(interval, "timed out while waiting on select")
error.set_backtrace(caller)
@queries.each do |_, connection| emit_resolve_error(connection, connection.peer.host, error) if @connections.delete(connection)
end
while (connection = @connections.shift)
emit_resolve_error(connection, connection.peer.host, error)
end
close_or_resolve
end
|
#interests ⇒ Object
93
94
95
96
97
|
# File 'lib/httpx/resolver/system.rb', line 93
def interests
return if @queries.empty?
:r
end
|
#lazy_resolve(connection) ⇒ Object
113
114
115
116
117
118
119
120
|
# File 'lib/httpx/resolver/system.rb', line 113
def lazy_resolve(connection)
@connections << connection
resolve
return if empty?
@current_session.select_resolver(self, @current_selector)
end
|
#multi ⇒ Object
57
58
59
|
# File 'lib/httpx/resolver/system.rb', line 57
def multi
self
end
|
#resolvers {|_self| ... } ⇒ Object
51
52
53
54
55
|
# File 'lib/httpx/resolver/system.rb', line 51
def resolvers
return enum_for(__method__) unless block_given?
yield self
end
|
#timeout ⇒ Object
99
100
101
102
103
104
105
106
107
108
109
110
111
|
# File 'lib/httpx/resolver/system.rb', line 99
def timeout
_, connection = @queries.first
return unless connection
timeouts = @timeouts[connection.peer.host]
return if timeouts.empty?
log(level: 2) { "resolver #{FAMILY_TYPES[@record_type]}: next timeout #{timeouts.first} secs... (#{timeouts.size - 1} left)" }
timeouts.first
end
|
#to_io ⇒ Object
81
82
83
|
# File 'lib/httpx/resolver/system.rb', line 81
def to_io
@pipe_read.to_io
end
|
#transition(nextstate) ⇒ void
This method returns an undefined value.
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
# File 'lib/httpx/resolver/system.rb', line 142
def transition(nextstate)
case nextstate
when :idle
@timeouts.clear
when :open
return unless @state == :idle
@pipe_read, @pipe_write = IO.pipe
when :closed
return unless @state == :open
@pipe_write.close
@pipe_read.close
end
@state = nextstate
end
|