changed timeout so it relies on a loop timeout and a global timeout; both can be disabled

This commit is contained in:
HoneyryderChuck 2018-01-13 15:42:34 +00:00
parent 8797eebbf2
commit 65722a3b12
7 changed files with 71 additions and 129 deletions

View File

@ -42,8 +42,8 @@ module HTTPX
branch(**options).request(verb, uri)
end
def timeout(klass, **options)
branch(timeout: Timeout.by(klass, **options))
def timeout(**args)
branch(timeout: args)
end
def headers(headers)

View File

@ -44,7 +44,7 @@ module HTTPX
:ssl => { alpn_protocols: %w[h2 http/1.1] },
:http2_settings => { settings_enable_push: 0 },
:fallback_protocol => "http/1.1",
:timeout => Timeout.by(:per_operation),
:timeout => Timeout.new,
:headers => {},
:max_concurrent_requests => MAX_CONCURRENT_REQUESTS,
:max_retries => MAX_RETRIES,
@ -65,8 +65,8 @@ module HTTPX
self.headers.merge(headers)
end
def_option(:timeout) do |type, opts|
self.timeout = Timeout.by(type, opts)
def_option(:timeout) do |opts|
self.timeout = Timeout.new(opts)
end
def_option(:max_concurrent_requests) do |num|
@ -98,7 +98,7 @@ module HTTPX
merged = h1.merge(h2) do |k, v1, v2|
case k
when :headers, :ssl, :http2_settings
when :headers, :ssl, :http2_settings, :timeout
v1.merge(v2)
else
v2

View File

@ -1,29 +1,70 @@
# frozen_string_literal: true
require "timeout"
module HTTPX
module Timeout
class << self
def by(type, **opts)
case type
when :null
Null.new(opts)
when :per_operation
PerOperation.new(opts)
when :global
Global.new(opts)
when Null, Global, PerOperation
type.new(opts)
when Hash # default way
PerOperation.new(type)
else
raise "#{type}: unrecognized timeout option"
end
end
class Timeout
LOOP_TIMEOUT = 5
def self.new(opts = {})
return opts if opts.is_a?(Timeout)
super
end
def initialize(loop_timeout: 5, total_timeout: nil)
@loop_timeout = loop_timeout
@total_timeout = total_timeout
reset_counter
end
def timeout
@loop_timeout || @total_timeout
ensure
log_time
end
def ==(other)
if other.is_a?(Timeout)
@loop_timeout == other.instance_variable_get(:@loop_timeout) &&
@total_timeout == other.instance_variable_get(:@total_timeout)
else
super
end
end
def merge(other)
case other
when Hash
timeout = Timeout.new(other)
merge(timeout)
when Timeout
loop_timeout = other.instance_variable_get(:@loop_timeout) || @loop_timeout
total_timeout = other.instance_variable_get(:@total_timeout) || @total_timeout
Timeout.new(loop_timeout: loop_timeout, total_timeout: total_timeout)
else
raise ArgumentError, "can't merge with #{other.class}"
end
end
private
def reset_counter
@time_left = @total_timeout
end
def reset_timer
@started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
def log_time
return unless @time_left
return reset_timer unless @started
@time_left -= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started)
if @time_left <= 0
raise TimeoutError, "Timed out after #{@total_timeout} seconds"
end
reset_timer
end
end
end
require "httpx/timeout/null"
require "httpx/timeout/per_operation"
require "httpx/timeout/global"

View File

@ -1,50 +0,0 @@
# frozen_string_literal: true
require "timeout"
module HTTPX::Timeout
class Global < PerOperation
TOTAL_TIMEOUT = 15
attr_reader :total_timeout
def initialize(total_timeout: TOTAL_TIMEOUT)
@total_timeout = total_timeout
reset_counter
@running = false
end
def ==(other)
other.is_a?(Global) &&
@total_timeout == other.total_timeout
end
def timeout
unless @running
reset_timer
@running = true
end
log_time
@time_left
end
private
def reset_counter
@time_left = @total_timeout
end
def reset_timer
@started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
def log_time
@time_left -= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started)
if @time_left <= 0
raise HTTPX::TimeoutError, "Timed out after using the allocated #{@total_timeout} seconds"
end
reset_timer
end
end
end

View File

@ -1,16 +0,0 @@
# frozen_string_literal: true
module HTTPX::Timeout
class Null
def initialize(**)
end
def ==(other)
other.is_a?(Null)
end
def timeout
nil
end
end
end

View File

@ -1,32 +0,0 @@
# frozen_string_literal: true
require "timeout"
module HTTPX::Timeout
class PerOperation < Null
OPERATION_TIMEOUT = 5
CONNECT_TIMEOUT = 5
attr_reader :connect_timeout, :operation_timeout
def initialize(connect: CONNECT_TIMEOUT,
operation: OPERATION_TIMEOUT)
@connect_timeout = connect
@operation_timeout = operation
@timeout = @connect_timeout
end
def timeout
timeout = @timeout
@timeout = @operation_timeout
timeout
end
def ==(other)
other.is_a?(PerOperation) &&
@connect_timeout == other.connect_timeout &&
@operation_timeout == other.operation_timeout
end
end
end

View File

@ -53,7 +53,6 @@ class OptionsSpec < Minitest::Test
:ssl => {:foo => "bar"},
)
assert foo.merge(bar).to_hash == {
:io => ENV.key?("HTTPX_DEBUG") ? $stderr : nil,
:debug => nil,
@ -65,7 +64,7 @@ class OptionsSpec < Minitest::Test
:window_size => 16_384,
:body_threshold_size => 114_688,
:form => {:bar => "bar"},
:timeout => Timeout::PerOperation.new,
:timeout => Timeout.new,
:ssl => {:foo => "bar", :alpn_protocols => %w[h2 http/1.1] },
:http2_settings => { :settings_enable_push => 0 },
:fallback_protocol => "http/1.1",