Home > Blockchain >  Exit early from Kernel#select on socket close
Exit early from Kernel#select on socket close

Time:11-04

I have a very simple socket app:

# Server
server = TCPServer.new(2000)
loop do
  client = server.accept
  sleep 10
end

# Client
s = TCPSocket.new('localhost', 2000)
th1 = Thread.new do
  begin
    Kernel.select([s], nil, [s], nil)
    puts 'Select returned'
  rescue => e
    puts "Select raised #{e}"
  end
end
th2 = Thread.new do
  sleep 0.5
  s.close
end
[th1, th2].map(&:join)

Now, what I want to happen is, if while the client is hanging waiting for the server to respond, the client's socket is closed by a different thread, the Kernel#select call should exit immediately and raise an error. This is what happens on MacOs, however on Linux, the call to select hangs indefinitely (or until the timeout is hit).

  1. Is there any way to get around this?
  2. I think the system call I need is poll(2), but that doesn't seem to be available in ruby?

Any help would be appreciated!

CodePudding user response:

Is there any way to get around this?

Instead of trying to interrupt the waiting thread by closing the local socket, include the read end of a pipe among the IO objects it is selecting on. Then another thread can interrupt the selection by writing to or closing the write end of the pipe. That has the added advantage that it allows the client to distinguish between a local termination request and a remote disconnection.

Something along these lines, for example:

rd, wr = IO.pipe

# ...

#
# thread 1
#

sock = # ...

# ...
ready = Kernel.select([sock, rd], nil, [sock, rd], timeout)
if ready.nil?
  # handle timeout ...
elsif ready.flatten.include? rd
  # handle local termination request ...
else
  # handle socket ...
end

#
# thread 2
#

# Interrupt thread 1
wr.close
  • Related