Project

General

Profile

Bug #13407

We have recv_nonblock but not send_nonblock... can we add it?

Added by ioquatix (Samuel Williams) about 2 years ago. Updated 9 months ago.

Status:
Assigned
Priority:
Normal
Target version:
-
[ruby-core:80593]

Description

We have recv_nonblock, read_nonblock, write_nonblock but not BasicSocket#send_nonblock. Is this a mistake?

History

Updated by normalperson (Eric Wong) about 2 years ago

samuel@oriontransfer.org wrote:

Bug #13407: We have recv_nonblock but not send_nonblock... can we add it?
https://bugs.ruby-lang.org/issues/13407

We have recv_nonblock, read_nonblock, write_nonblock but not BasicSocket#send_nonblock. Is this a mistake?

I've sometimes wondered that too...

However we have sendmsg_nonblock, is that insufficient?

So I also wonder why recv_nonblock exists, since
recvmsg_nonblock is a superset of its functionality;
and redundant methods waste memory...

It's too late to remove recv_nonblock, of course.

Updated by ioquatix (Samuel Williams) about 2 years ago

Thanks for the quick reply and interesting information.

I think I'd like to see send_nonblock, but I'm not sure if it's implementation is different than sendmsg_nonblock. If it's not different, an alias may be sufficient.

Updated by ioquatix (Samuel Williams) about 2 years ago

I've been playing around with sendmsg_nonblock, but I can't see how to make a wrapper around it with the equivalent API to send. It seems like it should be possible, but it always requires ancillary data, can't provide nil?

Updated by normalperson (Eric Wong) about 2 years ago

samuel@oriontransfer.org wrote:

I've been playing around with sendmsg_nonblock, but I can't
see how to make a wrapper around it with the equivalent API to
send. It seems like it should be possible, but it always
requires ancillary data, can't provide nil?

Huh? The following works for me:

require 'socket'
a, b = UNIXSocket.pair
b.sendmsg_nonblock("HI", exception: false)
p a.recv(2)
# prints "HI"

Updated by ioquatix (Samuel Williams) about 2 years ago

Okay, so I found the issue.

Firstly, sendmsg doesn't work for UDP sockets, I get EINVAL on Darwin OS.

Secondly, UDPSocket overrides #send but only in a specific case:

static VALUE
udp_send(int argc, VALUE *argv, VALUE sock)
{
    VALUE flags, host, port;
    struct udp_send_arg arg;
    VALUE ret;

    if (argc == 2 || argc == 3) {
    return rsock_bsock_send(argc, argv, sock);
    }
    rb_scan_args(argc, argv, "4", &arg.sarg.mesg, &flags, &host, &port);

    StringValue(arg.sarg.mesg);
    GetOpenFile(sock, arg.fptr);
    arg.sarg.fd = arg.fptr->fd;
    arg.sarg.flags = NUM2INT(flags);
    arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0);
    ret = rb_ensure(udp_send_internal, (VALUE)&arg,
            rsock_freeaddrinfo, (VALUE)arg.res);
    if (!ret) rsock_sys_fail_host_port("sendto(2)", host, port);
    return ret;
}

If you call udp_socket.send(data, flags, host, port) it uses sendto which works, otherwise it uses rsock_bsock_send which fails with EINVAL.

If you already constructed a sockaddr, for example, trying udp_socket.send(data, flags, sockaddr) will fail. It's all a bit confusing. Additionally, for UDPSocket, there is no send_nonblock which is my real issue here, and using sendmsg_nonblock fails.

Updated by normalperson (Eric Wong) about 2 years ago

samuel@oriontransfer.org wrote:

Firstly, sendmsg doesn't work for UDP sockets, I get EINVAL on Darwin OS.

Can you show us a sample code of what you're trying?

I wonder if it's something that works on Linux but not Darwin
or if there's something else... I don't have too much
experience with UDP, but maybe it's something I can still help
with.

Updated by ioquatix (Samuel Williams) about 2 years ago

Okay.

Here is the working example.

#!/usr/bin/env ruby

require 'socket'

port = 6778

server = UDPSocket.new.tap{|socket| socket.bind("localhost", port)}
client = UDPSocket.new

data = "Matz is nice so we are nice."

t1 = Thread.new do
    packet, (_, remote_port, remote_host) = server.recvfrom(512)
    server.send(packet, 0, remote_host, remote_port)
end

t2 = Thread.new do
    client.send(data, 0, "localhost", port)

    response, _ = client.recvfrom(512)

    puts "Got response: #{response.inspect}"
end

[t1, t2].each(&:join)

puts "Finished."

Here is one that fails with EINVAL:

#!/usr/bin/env ruby

Thread.abort_on_exception = true

require 'socket'

port = 6778

server = UDPSocket.new.tap{|socket| socket.bind("localhost", port)}
client = UDPSocket.new

data = "Matz is nice so we are nice."

t1 = Thread.new do
    puts "Server waiting for packet..."
    packet, (_, remote_port, remote_host) = server.recvfrom(512)
    server.send(packet, 0, remote_host, remote_port)
end

t2 = Thread.new do
    address = Addrinfo.udp("localhost", port)

    puts "Sending data to #{address.inspect}"
    # Should call ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen), but calls send which fails with EINVAL.
    result = client.send(data, 0, address.to_sockaddr)

    response, _ = client.recvfrom(512)

    puts "Got response: #{response.inspect}"
end

[t1, t2].each(&:join)

puts "Finished."

Updated by nobu (Nobuyoshi Nakada) about 2 years ago

Please file a new issue.

Updated by ioquatix (Samuel Williams) about 2 years ago

nobu (Nobuyoshi Nakada) here is the issue https://bugs.ruby-lang.org/issues/13409 for the specific problem mentioned above.

But, if possible, I'd like send_nonblock too :)

#10

Updated by akr (Akira Tanaka) almost 2 years ago

I cannot remember why I didn't add send_nonblock.

However I feel adding send_nonblock is considerable because
sendmsg is much more complex than send/sendto.

Updated by hsbt (Hiroshi SHIBATA) over 1 year ago

  • Assignee set to akr (Akira Tanaka)
  • Status changed from Open to Assigned

Updated by ioquatix (Samuel Williams) 9 months ago

We can close this issue, as there exists a satisfactory alternative.

Also available in: Atom PDF