Project

General

Profile

Actions

Feature #20215

open

Introduce `IO#readable?`

Added by ioquatix (Samuel Williams) 3 months ago. Updated 18 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:116456]

Description

There are some cases where, as an optimisation, it's useful to know whether more data is potentially available.

We already have IO#eof? but the problem with using IO#eof? is that it can block indefinitely for sockets.

Therefore, code which uses IO#eof? to determine if there is potentially more data, may hang.

def make_request(path = "/")
  client = connect_remote_host
  # HTTP/1.0 request:
  client.write("GET #{path} HTTP/1.0\r\n\r\n")

  # Read response
  client.gets("\r\n") # => "HTTP/1.0 200 OK\r\n"

  # Assuming connection close, there are two things the server can do:
  # 1. peer.close
  # 2. peer.write(...); peer.close

  if client.eof? # <--- Can hang here!
    puts "Connection closed"
    # Avoid yielding as we know there definitely won't be any data.
  else
    puts "Connection open, data may be available..."
    # There might be data available, so yield.
    yield(client)
  end
ensure
  client&.close
end

make_request do |client|
  puts client.read # <--- Prefer to wait here.
end

The proposed IO#readable? is similar to IO#eof? but rather than blocking, would simply return false. The expectation is the user will subsequently call read which may then wait.

The proposed implementation would look something like this:

class IO
  def readable?
    !self.closed?
  end
end

class BasicSocket
  # Is it likely that the socket is still connected?
  # May return false positive, but won't return false negative.
  def readable?
    return false unless super
    
    # If we can wait for the socket to become readable, we know that the socket may still be open.
    result = self.recv_nonblock(1, MSG_PEEK, exception: false)
    
    # No data was available - newer Ruby can return nil instead of empty string:
    return false if result.nil?
    
    # Either there was some data available, or we can wait to see if there is data avaialble.
    return !result.empty? || result == :wait_readable
    
  rescue Errno::ECONNRESET
    # This might be thrown by recv_nonblock.
    return false
  end
end

For IO itself, when there is buffered data, readable? would also return true immediately, similar to eof?. This is not shown in the above implementation as I'm not sure if there is any Ruby method which exposes "there is buffered data".

Actions

Also available in: Atom PDF

Like1
Like0Like0Like0Like0Like0Like0Like1Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0