class HTTP::Timeout::Global

def connect(socket_class, host, port, nodelay = false)

def connect(socket_class, host, port, nodelay = false)
  reset_timer
  ::Timeout.timeout(@time_left, ConnectTimeoutError) do
    @socket = socket_class.open(host, port)
    @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if nodelay
  end
  log_time
end

def connect_ssl

def connect_ssl
  reset_timer
  begin
    @socket.connect_nonblock
  rescue IO::WaitReadable
    wait_readable_or_timeout
    retry
  rescue IO::WaitWritable
    wait_writable_or_timeout
    retry
  end
end

def initialize(*args)

def initialize(*args)
  super
  @timeout = @time_left = options.fetch(:global_timeout)
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