class HTTP::Timeout::Global

def connect(socket_class, host_name, *args)

def connect(socket_class, host_name, *args)
  connect_operation = lambda do |host_address|
    ::Timeout.timeout(@time_left, TimeoutError) do
      super(socket_class, host_address, *args)
    end
  end
  host_addresses = @dns_resolver.call(host_name)
  # ensure something to iterates
  trying_targets = host_addresses.empty? ? [host_name] : host_addresses
  reset_timer
  trying_iterator = trying_targets.lazy
  error = nil
  begin
    connect_operation.call(trying_iterator.next).tap do
      log_time
    end
  rescue TimeoutError => e
    error = e
    retry
  rescue ::StopIteration
    raise error
  end
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
  @timeout = @time_left = options.fetch(:global_timeout)
  @dns_resolver = options.fetch(:dns_resolver) do
    ::Resolv.method(:getaddresses)
  end
end

def log_time

def log_time
  @time_left -= (Time.now - @started)
  raise TimeoutError, "Timed out after using the allocated #{@timeout} seconds" if @time_left <= 0
  reset_timer
end

def perform_io

Perform the given I/O operation with the given argument
def perform_io
  reset_timer
  loop do
    result = yield
    case result
    when :wait_readable then wait_readable_or_timeout
    when :wait_writable then wait_writable_or_timeout
    when NilClass       then return :eof
    else                return result
    end
  rescue IO::WaitReadable
    wait_readable_or_timeout
  rescue IO::WaitWritable
    wait_writable_or_timeout
  end
rescue EOFError
  :eof
end

def read_nonblock(size, buffer = nil)

def read_nonblock(size, buffer = nil)
  @socket.read_nonblock(size, buffer, :exception => false)
end

def readpartial(size, buffer = nil)

Read from the socket
def readpartial(size, buffer = nil)
  perform_io { read_nonblock(size, buffer) }
end

def reset_counter

To future me: Don't remove this again, past you was smarter.
def reset_counter
  @time_left = @timeout
end

def reset_timer

via method calls instead of a block to monitor.
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 wait_readable_or_timeout

Wait for a socket to become readable
def wait_readable_or_timeout
  @socket.to_io.wait_readable(@time_left)
  log_time
end

def wait_writable_or_timeout

Wait for a socket to become writable
def wait_writable_or_timeout
  @socket.to_io.wait_writable(@time_left)
  log_time
end

def write(data)

Write to the socket
def write(data)
  perform_io { write_nonblock(data) }
end

def write_nonblock(data)

def write_nonblock(data)
  @socket.write_nonblock(data, :exception => false)
end