Bug #19012
openBasicSocket#recv* methods return an empty packet instead of nil on closed connections
Description
man recvmsg(2)
states:
Return Value
These calls return the number of bytes received, or -1 if an error occurred. The return value will be 0 when the peer has performed an orderly shutdown.
But somehow the entire receiv
family of methods in Ruby seem to interpret 0
as empty string instead of "EOF".
require 'socket'
puts "=== pipes ==="
r, w = IO.pipe
r.read_nonblock(1, exception: false) # => :wait_readable
w.close
r.read_nonblock(1, exception: false) # => nil (EOF)
puts "=== sockets ===="
r, w = UNIXSocket.socketpair
r.read_nonblock(1, exception: false) # => :wait_readable
r.recvmsg_nonblock(1, exception: false) # => :wait_readable
r.recv_nonblock(1, exception: false) # => :wait_readable
w.close
r.read_nonblock(1, exception: false) # => nil (EOF)
r.recvmsg_nonblock(1, exception: false) # => ["", #<Addrinfo: empty-sockaddr SOCK_STREAM>, 128]]
r.recvmsg # => ["", #<Addrinfo: empty-sockaddr SOCK_STREAM>, 0]]
r.recv_nonblock(1, exception: false) # => ""
Expected behavior¶
I would expect recvmsg_nonblock
, recvmsg
, recv_nonblock
and recv
to return nil
when the connection is closed.
Updated by byroot (Jean Boussier) 8 months ago
I opened a PoC patch for it: https://github.com/ruby/ruby/pull/6407
Updated by byroot (Jean Boussier) 8 months ago
@akr (Akira Tanaka) pointed on the PR that this behavior might be desirable for "connection-less" sockets such as DGRAM
.
That said I think we should try to distinguish between "nothing was received" and "we received an empty packet".
I'm not quite familiar enough with recv
(yet) to know whether it's possible to make the difference though. I'll try to research this more when I have a bit of time.
Updated by mame (Yusuke Endoh) 6 months ago
This is what @akr (Akira Tanaka) said at the dev meeting. (My understanding)
The proposed behavior might be possible for stream. On the other hand, for datagram, the current behavior is better. I am not sure if there is a portable way to determine if the file descriptor behind an IO object is stream or datagram.
Updated by byroot (Jean Boussier) 6 months ago
I am not sure if there is a portable way to determine if the file descriptor behind an IO object is stream or datagram.
Yeah, me neither. I'll try to dig more.
Updated by byroot (Jean Boussier) 6 months ago
Apparently we can get this via getsockopt(fd, SOL_SOCKET, SO_TYPE, &type, &length);
, the question being how portable it is.
Updated by byroot (Jean Boussier) 6 months ago
I was able to implement the desired behavior in https://github.com/ruby/ruby/pull/6407