class HTTP::Timeout::Global
def connect(socket_class, host, port)
def connect(socket_class, host, port) reset_timer ::Timeout.timeout(time_left, TimeoutError) do @socket = socket_class.open(host, port) end log_time end
def connect_ssl
def connect_ssl reset_timer begin socket.connect_nonblock rescue IO::WaitReadable IO.select([socket], nil, nil, time_left) log_time retry rescue IO::WaitWritable IO.select(nil, [socket], nil, time_left) log_time retry end end
def initialize(*args)
def initialize(*args) super reset_counter end
def log_time
def log_time @time_left -= (Time.now - @started) if time_left <= 0 fail TimeoutError, "Timed out after using the allocated #{total_timeout} seconds" end reset_timer end
def readpartial(size)
def readpartial(size) reset_timer begin socket.read_nonblock(size) rescue IO::WaitReadable IO.select([socket], nil, nil, time_left) log_time retry end end
def reset_counter
def reset_counter @time_left = @total_timeout = connect_timeout + read_timeout + write_timeout end
def reset_timer
Due to the run/retry nature of nonblocking I/O, it's easier to keep track of time
def reset_timer @started = Time.now end
def write(data)
def write(data) reset_timer begin socket << data rescue IO::WaitWritable IO.select(nil, [socket], nil, time_left) log_time retry end end