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
  @time_left = connect_timeout + read_timeout + write_timeout
  @total_timeout = time_left
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 perform_io

Perform the given I/O operation with the given argument
def perform_io
  reset_timer
  loop do
    begin
      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
  end
rescue EOFError
  :eof
end

def read_nonblock(size)

def read_nonblock(size)
  @socket.read_nonblock(size)
end

def read_nonblock(size)

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

def readpartial(size)

Read from the socket
def readpartial(size)
  perform_io { read_nonblock(size) }
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
  IO.select([@socket], nil, nil, time_left)
  log_time
end

def wait_writable_or_timeout

Wait for a socket to become writable
def wait_writable_or_timeout
  IO.select(nil, [@socket], nil, 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)
end

def write_nonblock(data)

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