Project

General

Profile

Actions

Bug #19012

closed

BasicSocket#recv* methods return an empty packet instead of nil on closed connections

Added by byroot (Jean Boussier) over 1 year ago. Updated 2 months ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:109961]

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) over 1 year 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) about 1 year 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) about 1 year 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) about 1 year 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) about 1 year ago

I was able to implement the desired behavior in https://github.com/ruby/ruby/pull/6407

Actions #7

Updated by byroot (Jean Boussier) 6 months ago

  • Status changed from Open to Closed

Applied in changeset git|bcc905100f1079e191632cfd02319c10af82dac0.


BasicSocket#recv* return nil rather than an empty packet

[Bug #19012]

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.

Not too sure how one is supposed to make the difference between a packet of
size 0 and a closed connection.

Updated by zverok (Victor Shepelev) 2 months ago

Should the docs for BasicSocket#recv_nonblock be updated? Now they state

When recvfrom(2) returns 0, Socket#recv_nonblock returns an empty string as data. The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc.

I am not sure whether the change affects all the cases (this thing about datagrams got me confused).

Other affected methods don't have a similar clause, but this one seems to directly contradict the implemented behavior now.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0