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
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)
def readpartial(size, buffer = nil) perform_io { read_nonblock(size, buffer) } end
def reset_counter
def reset_counter @time_left = @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 wait_readable_or_timeout
def wait_readable_or_timeout @socket.to_io.wait_readable(@time_left) log_time end
def wait_writable_or_timeout
def wait_writable_or_timeout @socket.to_io.wait_writable(@time_left) log_time end
def write(data)
def write(data) perform_io { write_nonblock(data) } end
def write_nonblock(data)
def write_nonblock(data) @socket.write_nonblock(data, :exception => false) end