Project

General

Profile

Feature #11229 ยป 0001-socket-allow-exception-free-nonblocking-sendmsg-recv.patch

normalperson (Eric Wong), 06/05/2015 11:47 PM

View differences:

ext/socket/ancdata.c
#include <time.h>
int rsock_cmsg_cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */
static VALUE sym_exception, sym_wait_readable, sym_wait_writable;
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
static VALUE rb_cAncillaryData;
......
VALUE data, vflags, dest_sockaddr;
struct msghdr mh;
struct iovec iov;
VALUE opts = Qnil;
VALUE controls = Qnil;
int controls_num;
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
......
if (argc == 0)
rb_raise(rb_eArgError, "mesg argument required");
rb_scan_args(argc, argv, "12*", &data, &vflags, &dest_sockaddr, &controls);
rb_scan_args(argc, argv, "12*:", &data, &vflags, &dest_sockaddr, &controls,
&opts);
StringValue(data);
controls_num = RARRAY_LENINT(controls);
......
rb_io_check_closed(fptr);
goto retry;
}
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN))
rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "sendmsg(2) would block");
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
if (rsock_opt_false_p(opts, sym_exception)) {
return sym_wait_writable;
}
rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE,
"sendmsg(2) would block");
}
rb_sys_fail("sendmsg(2)");
}
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
......
#if defined(HAVE_SENDMSG)
/*
* call-seq:
* basicsocket.sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls) => numbytes_sent
* basicsocket.sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls, opts={}) => numbytes_sent
*
* sendmsg_nonblock sends a message using sendmsg(2) system call in non-blocking manner.
*
......
* but the non-blocking flag is set before the system call
* and it doesn't retry the system call.
*
* By specifying `exception: false`, the _opts_ hash allows you to indicate
* that sendmsg_nonblock should not raise an IO::WaitWritable exception, but
* return the symbol :wait_writable instead.
*/
VALUE
rsock_bsock_sendmsg_nonblock(int argc, VALUE *argv, VALUE sock)
......
rb_io_check_closed(fptr);
goto retry;
}
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN))
if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
if (rsock_opt_false_p(vopts, sym_exception)) {
return sym_wait_readable;
}
rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "recvmsg(2) would block");
}
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
if (!gc_done && (errno == EMFILE || errno == EMSGSIZE)) {
/*
......
* but non-blocking flag is set before the system call
* and it doesn't retry the system call.
*
* By specifying `exception: false`, the _opts_ hash allows you to indicate
* that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
* return the symbol :wait_writable instead.
*/
VALUE
rsock_bsock_recvmsg_nonblock(int argc, VALUE *argv, VALUE sock)
......
rb_define_method(rb_cAncillaryData, "ipv6_pktinfo_addr", ancillary_ipv6_pktinfo_addr, 0);
rb_define_method(rb_cAncillaryData, "ipv6_pktinfo_ifindex", ancillary_ipv6_pktinfo_ifindex, 0);
#endif
#undef rb_intern
sym_exception = ID2SYM(rb_intern("exception"));
sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
}
ext/socket/basicsocket.c
/*
* call-seq:
* basicsocket.recv_nonblock(maxlen) => mesg
* basicsocket.recv_nonblock(maxlen, flags) => mesg
* basicsocket.recv_nonblock(maxlen [, flags [, options ]) => mesg
*
* Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
* O_NONBLOCK is set for the underlying file descriptor.
......
* === Parameters
* * +maxlen+ - the number of bytes to receive from the socket
* * +flags+ - zero or more of the +MSG_+ options
* * +options+ - keyword hash, supporting `exception: false`
*
* === Example
* serv = TCPServer.new("127.0.0.1", 0)
......
* it is extended by IO::WaitReadable.
* So IO::WaitReadable can be used to rescue the exceptions for retrying recv_nonblock.
*
* By specifying `exception: false`, the options hash allows you to indicate
* that recv_nonblock should not raise an IO::WaitWritable exception, but
* return the symbol :wait_writable instead.
*
* === See
* * Socket#recvfrom
*/
ext/socket/init.c
long slen;
int fd, flags;
VALUE addr = Qnil;
VALUE opts = Qnil;
socklen_t len0;
rb_scan_args(argc, argv, "11", &len, &flg);
rb_scan_args(argc, argv, "11:", &len, &flg, &opts);
if (flg == Qnil) flags = 0;
else flags = NUM2INT(flg);
......
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
#endif
if (rsock_opt_false_p(opts, sym_exception))
return sym_wait_readable;
rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "recvfrom(2) would block");
}
rb_sys_fail("recvfrom(2)");
......
struct sockaddr *sockaddr, socklen_t *len)
{
int fd2;
int ex = 1;
VALUE opts = Qnil;
rb_scan_args(argc, argv, "0:", &opts);
if (!NIL_P(opts) && Qfalse == rb_hash_lookup2(opts, sym_exception, Qundef))
ex = 0;
rb_secure(3);
rb_io_set_nonblock(fptr);
fd2 = cloexec_accept(fptr->fd, (struct sockaddr*)sockaddr, len, 1);
......
#if defined EPROTO
case EPROTO:
#endif
if (!ex)
if (rsock_opt_false_p(opts, sym_exception))
return sym_wait_readable;
rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "accept(2) would block");
}
ext/socket/rubysocket.h
# define MSG_DONTWAIT_RELIABLE 0
#endif
static inline int
rsock_opt_false_p(VALUE opt, VALUE sym)
{
if (!NIL_P(opt) && Qfalse == rb_hash_lookup2(opt, sym, Qundef))
return 1;
return 0;
}
#endif
ext/socket/socket.c
n = connect(fptr->fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr));
if (n < 0) {
if (errno == EINPROGRESS) {
if (!NIL_P(opts) &&
Qfalse == rb_hash_lookup2(opts, sym_exception, Qundef)) {
if (rsock_opt_false_p(opts, sym_exception)) {
return sym_wait_writable;
}
rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "connect(2) would block");
}
if (errno == EISCONN) {
if (!NIL_P(opts) &&
Qfalse == rb_hash_lookup2(opts, sym_exception, Qundef)) {
if (rsock_opt_false_p(opts, sym_exception)) {
return INT2FIX(0);
}
}
ext/socket/udpsocket.c
/*
* call-seq:
* udpsocket.recvfrom_nonblock(maxlen) => [mesg, sender_inet_addr]
* udpsocket.recvfrom_nonblock(maxlen, flags) => [mesg, sender_inet_addr]
* udpsocket.recvfrom_nonblock(maxlen [, flags [, options]) => [mesg, sender_inet_addr]
*
* Receives up to _maxlen_ bytes from +udpsocket+ using recvfrom(2) after
* O_NONBLOCK is set for the underlying file descriptor.
......
* === Parameters
* * +maxlen+ - the number of bytes to receive from the socket
* * +flags+ - zero or more of the +MSG_+ options
* * +options+ - keyword hash, supporting `exception: false`
*
* === Example
* require 'socket'
......
* it is extended by IO::WaitReadable.
* So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
*
* By specifying `exception: false`, the options hash allows you to indicate
* that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
* return the symbol :wait_writable instead.
*
* === See
* * Socket#recvfrom
*/
test/socket/test_nonblock.rb
begin
require "socket"
require "io/nonblock"
require "io/wait"
rescue LoadError
end
......
}
end
def test_recvfrom_nonblock_no_exception
udp_pair do |s1, s2|
assert_equal :wait_readable, s1.recvfrom_nonblock(100, exception: false)
s2.send("aaa", 0, s1.getsockname)
assert s1.wait_readable
mesg, inet_addr = s1.recvfrom_nonblock(100, exception: false)
assert_equal(4, inet_addr.length)
assert_equal("aaa", mesg)
end
end
if defined?(UNIXSocket) && defined?(Socket::SOCK_SEQPACKET)
def test_sendmsg_nonblock_seqpacket
buf = '*' * 10000
......
rescue NotImplementedError, Errno::ENOSYS, Errno::EPROTONOSUPPORT
skip "UNIXSocket.pair(:SEQPACKET) not implemented on this platform: #{$!}"
end
def test_sendmsg_nonblock_no_exception
buf = '*' * 128
UNIXSocket.pair(:SEQPACKET) do |s1, s2|
n = 0
Timeout.timeout(60) do
case rv = s1.sendmsg_nonblock(buf, exception: false)
when Integer
n += rv
when :wait_writable
break
else
flunk "unexpected return value: #{rv.inspect}"
end while true
assert_equal :wait_writable, rv
assert_operator n, :>, 0
end
end
rescue NotImplementedError, Errno::ENOSYS, Errno::EPROTONOSUPPORT
skip "UNIXSocket.pair(:SEQPACKET) not implemented on this platform: #{$!}"
end
end
def test_recvmsg_nonblock_error
......
rescue Errno::EWOULDBLOCK
assert_kind_of(IO::WaitReadable, $!)
end
assert_equal :wait_readable, s1.recvmsg_nonblock(11, exception: false)
}
end
......
}
end
def test_recv_nonblock_no_exception
tcp_pair {|c, s|
assert_equal :wait_readable, c.recv_nonblock(11, exception: false)
s.write('HI')
assert c.wait_readable
assert_equal 'HI', c.recv_nonblock(11, exception: false)
assert_equal :wait_readable, c.recv_nonblock(11, exception: false)
}
end
def test_connect_nonblock_error
serv = TCPServer.new("127.0.0.1", 0)
_, port, _, _ = serv.addr
-
    (1-1/1)