2010/7/6 Eric Wong <redmine / ruby-lang.org>:
>
> sendfile() may return with a short write upon a client disconnect.  Instead of
> retrying and getting an error, Ruby tries to force a select() on the descriptor
> which fails to detect the disconnect.  This causes IO.copy_stream to hang,
> (possibly until TCP keepalives kick in).  IO.copy_stream should raise
> immediately.

Thank you for the reproducible script and fix.

I'll commit your fix.

However I think the Linux select behavior which doesn't notify writability on
disconnected TCP socket is suspicious.

  linux% ruby -rsocket -e '
  serv = TCPServer.open("127.0.0.1", 8888)
  s1 = TCPSocket.open("127.0.0.1", 8888)
  s2 = serv.accept
  s2.close
  s1.write "a" rescue p $!
  s1.write "a" rescue p $!
  p IO.select(nil, [s1], nil, 0)
  '
  #<Errno::EPIPE: Broken pipe>
  nil

FreeBSD and Solaris notify writability.

  freebsd% ruby -rsocket -e '
  serv = TCPServer.open("127.0.0.1", 8888)
  s1 = TCPSocket.open("127.0.0.1", 8888)
  s2 = serv.accept
  s2.close
  s1.write "a" rescue p $!
  s1.write "a" rescue p $!
  p IO.select(nil, [s1], nil, 0)
  '
  #<Errno::EPIPE: Broken pipe>
  [[], [#<TCPSocket:0x283263d8>], []]

  solaris% ruby -rsocket -e '
  serv = TCPServer.open("127.0.0.1", 8888)
  s1 = TCPSocket.open("127.0.0.1", 8888)
  s2 = serv.accept
  s2.close
  s1.write "a" rescue p $!
  s1.write "a" rescue p $!
  p IO.select(nil, [s1], nil, 0)
  '
  #<Errno::EPIPE: Broken pipe>
  [[], [#<TCPSocket:0x80e6e6c>], []]

I think select should notify writability when write would not block.
Cleary write doesn't block on disconnected socket.

Linux also notify writability for UNIX domain socket pair.

  linux% ruby -rsocket -e '
  s1, s2 = UNIXSocket.pair
  s2.close
  s1.write "a" rescue p $!
  p IO.select(nil, [s1], nil, 0)
  '
  #<Errno::EPIPE: Broken pipe>
  [[], [#<UNIXSocket:fd 3>], []]

I tested Linux 2.6.26.
-- 
Tanaka Akira