httpx 0.16.1 → 0.18.2

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/doc/release_notes/0_17_0.md +49 -0
  4. data/doc/release_notes/0_18_0.md +69 -0
  5. data/doc/release_notes/0_18_1.md +12 -0
  6. data/doc/release_notes/0_18_2.md +10 -0
  7. data/lib/httpx/adapters/datadog.rb +1 -1
  8. data/lib/httpx/adapters/faraday.rb +5 -3
  9. data/lib/httpx/adapters/webmock.rb +9 -3
  10. data/lib/httpx/altsvc.rb +2 -2
  11. data/lib/httpx/chainable.rb +4 -4
  12. data/lib/httpx/connection/http1.rb +23 -14
  13. data/lib/httpx/connection/http2.rb +35 -17
  14. data/lib/httpx/connection.rb +74 -76
  15. data/lib/httpx/domain_name.rb +1 -1
  16. data/lib/httpx/extensions.rb +50 -4
  17. data/lib/httpx/headers.rb +1 -1
  18. data/lib/httpx/io/ssl.rb +5 -1
  19. data/lib/httpx/io/tls.rb +7 -7
  20. data/lib/httpx/loggable.rb +5 -5
  21. data/lib/httpx/options.rb +35 -13
  22. data/lib/httpx/parser/http1.rb +10 -6
  23. data/lib/httpx/plugins/aws_sdk_authentication.rb +42 -18
  24. data/lib/httpx/plugins/aws_sigv4.rb +9 -11
  25. data/lib/httpx/plugins/compression.rb +5 -3
  26. data/lib/httpx/plugins/cookies/jar.rb +1 -1
  27. data/lib/httpx/plugins/digest_authentication.rb +4 -4
  28. data/lib/httpx/plugins/expect.rb +7 -3
  29. data/lib/httpx/plugins/grpc/message.rb +2 -2
  30. data/lib/httpx/plugins/grpc.rb +3 -3
  31. data/lib/httpx/plugins/h2c.rb +7 -3
  32. data/lib/httpx/plugins/internal_telemetry.rb +8 -8
  33. data/lib/httpx/plugins/multipart/decoder.rb +187 -0
  34. data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
  35. data/lib/httpx/plugins/multipart/part.rb +2 -2
  36. data/lib/httpx/plugins/multipart.rb +16 -2
  37. data/lib/httpx/plugins/ntlm_authentication.rb +4 -4
  38. data/lib/httpx/plugins/proxy/ssh.rb +11 -4
  39. data/lib/httpx/plugins/proxy.rb +6 -4
  40. data/lib/httpx/plugins/response_cache/store.rb +55 -0
  41. data/lib/httpx/plugins/response_cache.rb +88 -0
  42. data/lib/httpx/plugins/retries.rb +36 -14
  43. data/lib/httpx/plugins/stream.rb +3 -4
  44. data/lib/httpx/pool.rb +39 -13
  45. data/lib/httpx/registry.rb +1 -1
  46. data/lib/httpx/request.rb +12 -13
  47. data/lib/httpx/resolver/https.rb +5 -7
  48. data/lib/httpx/resolver/native.rb +4 -2
  49. data/lib/httpx/resolver/resolver_mixin.rb +2 -1
  50. data/lib/httpx/resolver/system.rb +2 -0
  51. data/lib/httpx/resolver.rb +2 -2
  52. data/lib/httpx/response.rb +60 -44
  53. data/lib/httpx/selector.rb +16 -19
  54. data/lib/httpx/session.rb +22 -15
  55. data/lib/httpx/session2.rb +1 -1
  56. data/lib/httpx/timers.rb +84 -0
  57. data/lib/httpx/transcoder/body.rb +2 -1
  58. data/lib/httpx/transcoder/form.rb +20 -0
  59. data/lib/httpx/transcoder/json.rb +12 -0
  60. data/lib/httpx/transcoder.rb +62 -1
  61. data/lib/httpx/utils.rb +10 -2
  62. data/lib/httpx/version.rb +1 -1
  63. data/lib/httpx.rb +1 -0
  64. data/sig/buffer.rbs +2 -2
  65. data/sig/chainable.rbs +7 -1
  66. data/sig/connection/http1.rbs +15 -4
  67. data/sig/connection/http2.rbs +19 -5
  68. data/sig/connection.rbs +15 -9
  69. data/sig/headers.rbs +19 -18
  70. data/sig/options.rbs +13 -5
  71. data/sig/parser/http1.rbs +3 -3
  72. data/sig/plugins/aws_sdk_authentication.rbs +22 -4
  73. data/sig/plugins/aws_sigv4.rbs +12 -3
  74. data/sig/plugins/basic_authentication.rbs +1 -1
  75. data/sig/plugins/multipart.rbs +64 -8
  76. data/sig/plugins/proxy.rbs +6 -6
  77. data/sig/plugins/response_cache.rbs +35 -0
  78. data/sig/plugins/retries.rbs +3 -0
  79. data/sig/pool.rbs +6 -0
  80. data/sig/request.rbs +11 -8
  81. data/sig/resolver/native.rbs +2 -1
  82. data/sig/resolver/resolver_mixin.rbs +1 -1
  83. data/sig/resolver/system.rbs +3 -1
  84. data/sig/response.rbs +11 -4
  85. data/sig/selector.rbs +8 -6
  86. data/sig/session.rbs +8 -14
  87. data/sig/timers.rbs +32 -0
  88. data/sig/transcoder/form.rbs +1 -0
  89. data/sig/transcoder/json.rbs +1 -0
  90. data/sig/transcoder.rbs +5 -4
  91. data/sig/utils.rbs +4 -0
  92. metadata +18 -17
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Plugins
5
+ #
6
+ # This plugin adds support for retrying requests when certain errors happen.
7
+ #
8
+ # https://gitlab.com/honeyryderchuck/httpx/wikis/Response-Cache
9
+ #
10
+ module ResponseCache
11
+ CACHEABLE_VERBS = %i[get head].freeze
12
+ private_constant :CACHEABLE_VERBS
13
+
14
+ class << self
15
+ def load_dependencies(*)
16
+ require_relative "response_cache/store"
17
+ end
18
+
19
+ def cacheable_request?(request)
20
+ CACHEABLE_VERBS.include?(request.verb)
21
+ end
22
+
23
+ def cacheable_response?(response)
24
+ response.is_a?(Response) &&
25
+ # partial responses shall not be cached, only full ones.
26
+ response.status != 206 && (
27
+ response.headers.key?("etag") || response.headers.key?("last-modified-at")
28
+ )
29
+ end
30
+
31
+ def cached_response?(response)
32
+ response.is_a?(Response) && response.status == 304
33
+ end
34
+
35
+ def extra_options(options)
36
+ options.merge(response_cache_store: Store.new)
37
+ end
38
+ end
39
+
40
+ module OptionsMethods
41
+ def option_response_cache_store(value)
42
+ raise TypeError, "must be an instance of #{Store}" unless value.is_a?(Store)
43
+
44
+ value
45
+ end
46
+ end
47
+
48
+ module InstanceMethods
49
+ def clear_response_cache
50
+ @options.response_cache_store.clear
51
+ end
52
+
53
+ def build_request(*)
54
+ request = super
55
+ return request unless ResponseCache.cacheable_request?(request) && @options.response_cache_store.cached?(request.uri)
56
+
57
+ @options.response_cache_store.prepare(request)
58
+
59
+ request
60
+ end
61
+
62
+ def fetch_response(request, *)
63
+ response = super
64
+
65
+ if response && ResponseCache.cached_response?(response)
66
+ log { "returning cached response for #{request.uri}" }
67
+ cached_response = @options.response_cache_store.lookup(request.uri)
68
+
69
+ response.copy_from_cached(cached_response)
70
+ end
71
+
72
+ @options.response_cache_store.cache(request.uri, response) if response && ResponseCache.cacheable_response?(response)
73
+
74
+ response
75
+ end
76
+ end
77
+
78
+ module ResponseMethods
79
+ def copy_from_cached(other)
80
+ @body = other.body
81
+
82
+ @body.__send__(:rewind)
83
+ end
84
+ end
85
+ end
86
+ register_plugin :response_cache, ResponseCache
87
+ end
88
+ end
@@ -12,19 +12,29 @@ module HTTPX
12
12
  # TODO: pass max_retries in a configure/load block
13
13
 
14
14
  IDEMPOTENT_METHODS = %i[get options head put delete].freeze
15
- RETRYABLE_ERRORS = [IOError,
16
- EOFError,
17
- Errno::ECONNRESET,
18
- Errno::ECONNABORTED,
19
- Errno::EPIPE,
20
- TLSError,
21
- TimeoutError,
22
- Parser::Error,
23
- Errno::EINVAL,
24
- Errno::ETIMEDOUT].freeze
25
-
26
- def self.extra_options(options)
27
- options.merge(max_retries: MAX_RETRIES)
15
+ RETRYABLE_ERRORS = [
16
+ IOError,
17
+ EOFError,
18
+ Errno::ECONNRESET,
19
+ Errno::ECONNABORTED,
20
+ Errno::EPIPE,
21
+ Errno::EINVAL,
22
+ Errno::ETIMEDOUT,
23
+ Parser::Error,
24
+ TLSError,
25
+ TimeoutError,
26
+ Connection::HTTP2::GoawayError,
27
+ ].freeze
28
+ DEFAULT_JITTER = ->(interval) { interval * (0.5 * (1 + rand)) }
29
+
30
+ if ENV.key?("HTTPX_NO_JITTER")
31
+ def self.extra_options(options)
32
+ options.merge(max_retries: MAX_RETRIES)
33
+ end
34
+ else
35
+ def self.extra_options(options)
36
+ options.merge(max_retries: MAX_RETRIES, retry_jitter: DEFAULT_JITTER)
37
+ end
28
38
  end
29
39
 
30
40
  module OptionsMethods
@@ -38,6 +48,13 @@ module HTTPX
38
48
  value
39
49
  end
40
50
 
51
+ def option_retry_jitter(value)
52
+ # return early if callable
53
+ raise TypeError, ":retry_jitter must be callable" unless value.respond_to?(:call)
54
+
55
+ value
56
+ end
57
+
41
58
  def option_max_retries(value)
42
59
  num = Integer(value)
43
60
  raise TypeError, ":max_retries must be positive" unless num.positive?
@@ -87,10 +104,15 @@ module HTTPX
87
104
  retry_after = retry_after.call(request, response) if retry_after.respond_to?(:call)
88
105
 
89
106
  if retry_after
107
+ # apply jitter
108
+ if (jitter = request.options.retry_jitter)
109
+ retry_after = jitter.call(retry_after)
110
+ end
90
111
 
112
+ retry_start = Utils.now
91
113
  log { "retrying after #{retry_after} secs..." }
92
114
  pool.after(retry_after) do
93
- log { "retrying!!" }
115
+ log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
94
116
  connection = find_connection(request, connections, options)
95
117
  connection.send(request)
96
118
  end
@@ -6,11 +6,10 @@ module HTTPX
6
6
  @request = request
7
7
  @session = session
8
8
  @connections = connections
9
- @options = @request.options
10
9
  end
11
10
 
12
11
  def each(&block)
13
- return enum_for(__method__) unless block_given?
12
+ return enum_for(__method__) unless block
14
13
 
15
14
  raise Error, "response already streamed" if @response
16
15
 
@@ -72,7 +71,7 @@ module HTTPX
72
71
  private
73
72
 
74
73
  def response
75
- @session.__send__(:receive_requests, [@request], @connections, @options) until @request.response
74
+ @session.__send__(:receive_requests, [@request], @connections) until @request.response
76
75
 
77
76
  @request.response
78
77
  end
@@ -106,7 +105,7 @@ module HTTPX
106
105
 
107
106
  request = requests.first
108
107
 
109
- connections = _send_requests(requests, request.options)
108
+ connections = _send_requests(requests)
110
109
 
111
110
  StreamResponse.new(request, self, connections)
112
111
  end
data/lib/httpx/pool.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "forwardable"
4
- require "timers"
5
4
  require "httpx/selector"
6
5
  require "httpx/connection"
7
6
  require "httpx/resolver"
8
7
 
9
8
  module HTTPX
10
9
  class Pool
10
+ using ArrayExtensions
11
11
  extend Forwardable
12
12
 
13
13
  def_delegator :@timers, :after
@@ -15,7 +15,7 @@ module HTTPX
15
15
  def initialize
16
16
  @resolvers = {}
17
17
  @_resolver_ios = {}
18
- @timers = Timers::Group.new
18
+ @timers = Timers.new
19
19
  @selector = Selector.new
20
20
  @connections = []
21
21
  @connected_connections = 0
@@ -27,15 +27,18 @@ module HTTPX
27
27
 
28
28
  def next_tick
29
29
  catch(:jump_tick) do
30
- timeout = [next_timeout, @timers.wait_interval].compact.min
30
+ timeout = next_timeout
31
31
  if timeout && timeout.negative?
32
32
  @timers.fire
33
33
  throw(:jump_tick)
34
34
  end
35
35
 
36
- @selector.select(timeout, &:call)
37
-
38
- @timers.fire
36
+ begin
37
+ @selector.select(timeout, &:call)
38
+ @timers.fire
39
+ rescue TimeoutError => e
40
+ @timers.fire(e)
41
+ end
39
42
  end
40
43
  rescue StandardError => e
41
44
  @connections.each do |connection|
@@ -64,6 +67,16 @@ module HTTPX
64
67
  connection.on(:open) do
65
68
  @connected_connections += 1
66
69
  end
70
+ connection.on(:activate) do
71
+ select_connection(connection)
72
+ end
73
+ end
74
+
75
+ def deactivate(connections)
76
+ connections.each do |connection|
77
+ connection.deactivate
78
+ deselect_connection(connection) if connection.state == :inactive
79
+ end
67
80
  end
68
81
 
69
82
  # opens a connection to the IP reachable through +uri+.
@@ -81,7 +94,7 @@ module HTTPX
81
94
  def resolve_connection(connection)
82
95
  @connections << connection unless @connections.include?(connection)
83
96
 
84
- if connection.addresses || connection.state == :open
97
+ if connection.addresses || connection.open?
85
98
  #
86
99
  # there are two cases in which we want to activate initialization of
87
100
  # connection immediately:
@@ -98,7 +111,7 @@ module HTTPX
98
111
  resolver << connection
99
112
  return if resolver.empty?
100
113
 
101
- @_resolver_ios[resolver] ||= @selector.register(resolver)
114
+ @_resolver_ios[resolver] ||= select_connection(resolver)
102
115
  end
103
116
 
104
117
  def on_resolver_connection(connection)
@@ -107,7 +120,7 @@ module HTTPX
107
120
  end
108
121
  return register_connection(connection) unless found_connection
109
122
 
110
- if found_connection.state == :open
123
+ if found_connection.open?
111
124
  coalesce_connections(found_connection, connection)
112
125
  throw(:coalesced, found_connection)
113
126
  else
@@ -129,7 +142,7 @@ module HTTPX
129
142
 
130
143
  @resolvers.delete(resolver_type)
131
144
 
132
- @selector.deregister(resolver)
145
+ deselect_connection(resolver)
133
146
  @_resolver_ios.delete(resolver)
134
147
  resolver.close unless resolver.closed?
135
148
  end
@@ -140,7 +153,7 @@ module HTTPX
140
153
  # consider it connected already.
141
154
  @connected_connections += 1
142
155
  end
143
- @selector.register(connection)
156
+ select_connection(connection)
144
157
  connection.on(:close) do
145
158
  unregister_connection(connection)
146
159
  end
@@ -148,10 +161,18 @@ module HTTPX
148
161
 
149
162
  def unregister_connection(connection)
150
163
  @connections.delete(connection)
151
- @selector.deregister(connection)
164
+ deselect_connection(connection)
152
165
  @connected_connections -= 1
153
166
  end
154
167
 
168
+ def select_connection(connection)
169
+ @selector.register(connection)
170
+ end
171
+
172
+ def deselect_connection(connection)
173
+ @selector.deregister(connection)
174
+ end
175
+
155
176
  def coalesce_connections(conn1, conn2)
156
177
  if conn1.coalescable?(conn2)
157
178
  conn1.merge(conn2)
@@ -162,7 +183,11 @@ module HTTPX
162
183
  end
163
184
 
164
185
  def next_timeout
165
- @resolvers.values.reject(&:closed?).map(&:timeout).compact.min || @connections.map(&:timeout).compact.min
186
+ [
187
+ @timers.wait_interval,
188
+ *@resolvers.values.reject(&:closed?).filter_map(&:timeout),
189
+ *@connections.filter_map(&:timeout),
190
+ ].compact.min
166
191
  end
167
192
 
168
193
  def find_resolver_for(connection)
@@ -172,6 +197,7 @@ module HTTPX
172
197
 
173
198
  @resolvers[resolver_type] ||= begin
174
199
  resolver = resolver_type.new(connection_options)
200
+ resolver.pool = self if resolver.respond_to?(:pool=)
175
201
  resolver.on(:resolve, &method(:on_resolver_connection))
176
202
  resolver.on(:error, &method(:on_resolver_error))
177
203
  resolver.on(:close) { on_resolver_close(resolver) }
@@ -59,7 +59,7 @@ module HTTPX
59
59
  @registry ||= {}
60
60
  return @registry if tag.nil?
61
61
 
62
- handler = @registry.fetch(tag)
62
+ handler = @registry[tag]
63
63
  raise(Error, "#{tag} is not registered in #{self}") unless handler
64
64
 
65
65
  handler
data/lib/httpx/request.rb CHANGED
@@ -41,16 +41,15 @@ module HTTPX
41
41
 
42
42
  def_delegator :@body, :empty?
43
43
 
44
- def_delegator :@body, :chunk!
45
-
46
44
  def initialize(verb, uri, options = {})
47
45
  @verb = verb.to_s.downcase.to_sym
48
46
  @options = Options.new(options)
49
47
  @uri = Utils.to_uri(uri)
50
48
  if @uri.relative?
51
- raise(Error, "invalid URI: #{@uri}") unless @options.origin
49
+ origin = @options.origin
50
+ raise(Error, "invalid URI: #{@uri}") unless origin
52
51
 
53
- @uri = @options.origin.merge(@uri)
52
+ @uri = origin.merge(@uri)
54
53
  end
55
54
 
56
55
  raise(Error, "unknown method: #{verb}") unless METHODS.include?(@verb)
@@ -98,7 +97,7 @@ module HTTPX
98
97
  def response=(response)
99
98
  return unless response
100
99
 
101
- if response.status == 100
100
+ if response.is_a?(Response) && response.status == 100
102
101
  @informational_status = response.status
103
102
  return
104
103
  end
@@ -149,16 +148,16 @@ module HTTPX
149
148
  # :nocov:
150
149
  def inspect
151
150
  "#<HTTPX::Request:#{object_id} " \
152
- "#{@verb.to_s.upcase} " \
153
- "#{uri} " \
154
- "@headers=#{@headers} " \
155
- "@body=#{@body}>"
151
+ "#{@verb.to_s.upcase} " \
152
+ "#{uri} " \
153
+ "@headers=#{@headers} " \
154
+ "@body=#{@body}>"
156
155
  end
157
156
  # :nocov:
158
157
 
159
158
  class Body < SimpleDelegator
160
159
  class << self
161
- def new(*, options)
160
+ def new(_, options)
162
161
  return options.body if options.body.is_a?(self)
163
162
 
164
163
  super
@@ -182,7 +181,7 @@ module HTTPX
182
181
  end
183
182
 
184
183
  def each(&block)
185
- return enum_for(__method__) unless block_given?
184
+ return enum_for(__method__) unless block
186
185
  return if @body.nil?
187
186
 
188
187
  body = stream(@body)
@@ -223,7 +222,7 @@ module HTTPX
223
222
  def unbounded_body?
224
223
  return @unbounded_body if defined?(@unbounded_body)
225
224
 
226
- @unbounded_body = (chunked? || @body.bytesize == Float::INFINITY)
225
+ @unbounded_body = !@body.nil? && (chunked? || @body.bytesize == Float::INFINITY)
227
226
  end
228
227
 
229
228
  def chunked?
@@ -237,7 +236,7 @@ module HTTPX
237
236
  # :nocov:
238
237
  def inspect
239
238
  "#<HTTPX::Request::Body:#{object_id} " \
240
- "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
239
+ "#{unbounded_body? ? "stream" : "@bytesize=#{bytesize}"}>"
241
240
  end
242
241
  # :nocov:
243
242
  end
@@ -24,7 +24,9 @@ module HTTPX
24
24
  record_types: RECORD_TYPES.keys,
25
25
  }.freeze
26
26
 
27
- def_delegators :@resolver_connection, :connecting?, :to_io, :call, :close
27
+ def_delegators :@resolver_connection, :state, :connecting?, :to_io, :call, :close
28
+
29
+ attr_writer :pool
28
30
 
29
31
  def initialize(options)
30
32
  @options = Options.new(options)
@@ -63,15 +65,11 @@ module HTTPX
63
65
 
64
66
  private
65
67
 
66
- def pool
67
- Thread.current[:httpx_connection_pool] ||= Pool.new
68
- end
69
-
70
68
  def resolver_connection
71
- @resolver_connection ||= pool.find_connection(@uri, @options) || begin
69
+ @resolver_connection ||= @pool.find_connection(@uri, @options) || begin
72
70
  @building_connection = true
73
71
  connection = @options.connection_class.new("ssl", @uri, @options.merge(ssl: { alpn_protocols: %w[h2] }))
74
- pool.init_connection(connection, @options)
72
+ @pool.init_connection(connection, @options)
75
73
  emit_addresses(connection, @uri_addresses)
76
74
  @building_connection = false
77
75
  connection
@@ -47,6 +47,8 @@ module HTTPX
47
47
 
48
48
  def_delegator :@connections, :empty?
49
49
 
50
+ attr_reader :state
51
+
50
52
  def initialize(options)
51
53
  @options = Options.new(options)
52
54
  @ns_index = 0
@@ -120,7 +122,7 @@ module HTTPX
120
122
  def timeout
121
123
  return if @connections.empty?
122
124
 
123
- @start_timeout = Process.clock_gettime(Process::CLOCK_MONOTONIC)
125
+ @start_timeout = Utils.now
124
126
  hosts = @queries.keys
125
127
  @timeouts.values_at(*hosts).reject(&:empty?).map(&:first).min
126
128
  end
@@ -140,7 +142,7 @@ module HTTPX
140
142
  def do_retry
141
143
  return if @queries.empty?
142
144
 
143
- loop_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start_timeout
145
+ loop_time = Utils.elapsed_time(@start_timeout)
144
146
  connections = []
145
147
  queries = {}
146
148
  while (query = @queries.shift)
@@ -9,7 +9,7 @@ module HTTPX
9
9
  include Callbacks
10
10
  include Loggable
11
11
 
12
- CHECK_IF_IP = proc do |name|
12
+ CHECK_IF_IP = lambda do |name|
13
13
  begin
14
14
  IPAddr.new(name)
15
15
  true
@@ -55,6 +55,7 @@ module HTTPX
55
55
  return if ips.empty?
56
56
 
57
57
  ips.map { |ip| IPAddr.new(ip) }
58
+ rescue IOError
58
59
  end
59
60
 
60
61
  def emit_resolve_error(connection, hostname = connection.origin.host, ex = nil)
@@ -12,6 +12,8 @@ module HTTPX
12
12
  Resolv::DNS::EncodeError,
13
13
  Resolv::DNS::DecodeError].freeze
14
14
 
15
+ attr_reader :state
16
+
15
17
  def initialize(options)
16
18
  @options = Options.new(options)
17
19
  @resolver_options = @options.resolver_options
@@ -26,14 +26,14 @@ module HTTPX
26
26
  module_function
27
27
 
28
28
  def cached_lookup(hostname)
29
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
29
+ now = Utils.now
30
30
  @lookup_mutex.synchronize do
31
31
  lookup(hostname, now)
32
32
  end
33
33
  end
34
34
 
35
35
  def cached_lookup_set(hostname, entries)
36
- now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
36
+ now = Utils.now
37
37
  entries.each do |entry|
38
38
  entry["TTL"] += now
39
39
  end