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
3 3
#include <time.h>
4 4

  
5 5
int rsock_cmsg_cloexec_state = -1; /* <0: unknown, 0: ignored, >0: working */
6
static VALUE sym_exception, sym_wait_readable, sym_wait_writable;
6 7

  
7 8
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
8 9
static VALUE rb_cAncillaryData;
......
1133 1134
    VALUE data, vflags, dest_sockaddr;
1134 1135
    struct msghdr mh;
1135 1136
    struct iovec iov;
1137
    VALUE opts = Qnil;
1136 1138
    VALUE controls = Qnil;
1137 1139
    int controls_num;
1138 1140
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
......
1152 1154
    if (argc == 0)
1153 1155
        rb_raise(rb_eArgError, "mesg argument required");
1154 1156

  
1155
    rb_scan_args(argc, argv, "12*", &data, &vflags, &dest_sockaddr, &controls);
1157
    rb_scan_args(argc, argv, "12*:", &data, &vflags, &dest_sockaddr, &controls,
1158
                 &opts);
1156 1159

  
1157 1160
    StringValue(data);
1158 1161
    controls_num = RARRAY_LENINT(controls);
......
1281 1284
            rb_io_check_closed(fptr);
1282 1285
            goto retry;
1283 1286
        }
1284
        if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN))
1285
            rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "sendmsg(2) would block");
1287
        if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
1288
	    if (rsock_opt_false_p(opts, sym_exception)) {
1289
		return sym_wait_writable;
1290
	    }
1291
	    rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE,
1292
				  "sendmsg(2) would block");
1293
	}
1286 1294
	rb_sys_fail("sendmsg(2)");
1287 1295
    }
1288 1296
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
......
1336 1344
#if defined(HAVE_SENDMSG)
1337 1345
/*
1338 1346
 * call-seq:
1339
 *    basicsocket.sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls) => numbytes_sent
1347
 *    basicsocket.sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls, opts={}) => numbytes_sent
1340 1348
 *
1341 1349
 * sendmsg_nonblock sends a message using sendmsg(2) system call in non-blocking manner.
1342 1350
 *
......
1344 1352
 * but the non-blocking flag is set before the system call
1345 1353
 * and it doesn't retry the system call.
1346 1354
 *
1355
 * By specifying `exception: false`, the _opts_ hash allows you to indicate
1356
 * that sendmsg_nonblock should not raise an IO::WaitWritable exception, but
1357
 * return the symbol :wait_writable instead.
1347 1358
 */
1348 1359
VALUE
1349 1360
rsock_bsock_sendmsg_nonblock(int argc, VALUE *argv, VALUE sock)
......
1602 1613
            rb_io_check_closed(fptr);
1603 1614
            goto retry;
1604 1615
        }
1605
        if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN))
1616
        if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) {
1617
            if (rsock_opt_false_p(vopts, sym_exception)) {
1618
                return sym_wait_readable;
1619
            }
1606 1620
            rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "recvmsg(2) would block");
1621
        }
1607 1622
#if defined(HAVE_STRUCT_MSGHDR_MSG_CONTROL)
1608 1623
        if (!gc_done && (errno == EMFILE || errno == EMSGSIZE)) {
1609 1624
          /*
......
1788 1803
 * but non-blocking flag is set before the system call
1789 1804
 * and it doesn't retry the system call.
1790 1805
 *
1806
 * By specifying `exception: false`, the _opts_ hash allows you to indicate
1807
 * that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
1808
 * return the symbol :wait_writable instead.
1791 1809
 */
1792 1810
VALUE
1793 1811
rsock_bsock_recvmsg_nonblock(int argc, VALUE *argv, VALUE sock)
......
1833 1851
    rb_define_method(rb_cAncillaryData, "ipv6_pktinfo_addr", ancillary_ipv6_pktinfo_addr, 0);
1834 1852
    rb_define_method(rb_cAncillaryData, "ipv6_pktinfo_ifindex", ancillary_ipv6_pktinfo_ifindex, 0);
1835 1853
#endif
1854
#undef rb_intern
1855
    sym_exception = ID2SYM(rb_intern("exception"));
1856
    sym_wait_readable = ID2SYM(rb_intern("wait_readable"));
1857
    sym_wait_writable = ID2SYM(rb_intern("wait_writable"));
1836 1858
}
ext/socket/basicsocket.c
640 640

  
641 641
/*
642 642
 * call-seq:
643
 * 	basicsocket.recv_nonblock(maxlen) => mesg
644
 * 	basicsocket.recv_nonblock(maxlen, flags) => mesg
643
 * 	basicsocket.recv_nonblock(maxlen [, flags [, options ]) => mesg
645 644
 *
646 645
 * Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
647 646
 * O_NONBLOCK is set for the underlying file descriptor.
......
655 654
 * === Parameters
656 655
 * * +maxlen+ - the number of bytes to receive from the socket
657 656
 * * +flags+ - zero or more of the +MSG_+ options
657
 * * +options+ - keyword hash, supporting `exception: false`
658 658
 *
659 659
 * === Example
660 660
 * 	serv = TCPServer.new("127.0.0.1", 0)
......
679 679
 * it is extended by IO::WaitReadable.
680 680
 * So IO::WaitReadable can be used to rescue the exceptions for retrying recv_nonblock.
681 681
 *
682
 * By specifying `exception: false`, the options hash allows you to indicate
683
 * that recv_nonblock should not raise an IO::WaitWritable exception, but
684
 * return the symbol :wait_writable instead.
685
 *
682 686
 * === See
683 687
 * * Socket#recvfrom
684 688
 */
ext/socket/init.c
188 188
    long slen;
189 189
    int fd, flags;
190 190
    VALUE addr = Qnil;
191
    VALUE opts = Qnil;
191 192
    socklen_t len0;
192 193

  
193
    rb_scan_args(argc, argv, "11", &len, &flg);
194
    rb_scan_args(argc, argv, "11:", &len, &flg, &opts);
194 195

  
195 196
    if (flg == Qnil) flags = 0;
196 197
    else             flags = NUM2INT(flg);
......
226 227
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
227 228
	  case EWOULDBLOCK:
228 229
#endif
230
            if (rsock_opt_false_p(opts, sym_exception))
231
		return sym_wait_readable;
229 232
            rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "recvfrom(2) would block");
230 233
	}
231 234
	rb_sys_fail("recvfrom(2)");
......
528 531
			struct sockaddr *sockaddr, socklen_t *len)
529 532
{
530 533
    int fd2;
531
    int ex = 1;
532 534
    VALUE opts = Qnil;
533 535

  
534 536
    rb_scan_args(argc, argv, "0:", &opts);
535 537

  
536
    if (!NIL_P(opts) && Qfalse == rb_hash_lookup2(opts, sym_exception, Qundef))
537
	ex = 0;
538

  
539 538
    rb_secure(3);
540 539
    rb_io_set_nonblock(fptr);
541 540
    fd2 = cloexec_accept(fptr->fd, (struct sockaddr*)sockaddr, len, 1);
......
549 548
#if defined EPROTO
550 549
	  case EPROTO:
551 550
#endif
552
            if (!ex)
551
            if (rsock_opt_false_p(opts, sym_exception))
553 552
		return sym_wait_readable;
554 553
            rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "accept(2) would block");
555 554
	}
ext/socket/rubysocket.h
423 423
#  define MSG_DONTWAIT_RELIABLE 0
424 424
#endif
425 425

  
426
static inline int
427
rsock_opt_false_p(VALUE opt, VALUE sym)
428
{
429
    if (!NIL_P(opt) && Qfalse == rb_hash_lookup2(opt, sym, Qundef))
430
	return 1;
431
    return 0;
432
}
433

  
426 434
#endif
ext/socket/socket.c
503 503
    n = connect(fptr->fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr));
504 504
    if (n < 0) {
505 505
        if (errno == EINPROGRESS) {
506
            if (!NIL_P(opts) &&
507
                    Qfalse == rb_hash_lookup2(opts, sym_exception, Qundef)) {
506
	    if (rsock_opt_false_p(opts, sym_exception)) {
508 507
                return sym_wait_writable;
509 508
            }
510 509
            rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "connect(2) would block");
511 510
	}
512 511
	if (errno == EISCONN) {
513
            if (!NIL_P(opts) &&
514
                    Qfalse == rb_hash_lookup2(opts, sym_exception, Qundef)) {
512
	    if (rsock_opt_false_p(opts, sym_exception)) {
515 513
                return INT2FIX(0);
516 514
            }
517 515
	}
ext/socket/udpsocket.c
194 194

  
195 195
/*
196 196
 * call-seq:
197
 *   udpsocket.recvfrom_nonblock(maxlen) => [mesg, sender_inet_addr]
198
 *   udpsocket.recvfrom_nonblock(maxlen, flags) => [mesg, sender_inet_addr]
197
 *   udpsocket.recvfrom_nonblock(maxlen [, flags [, options]) => [mesg, sender_inet_addr]
199 198
 *
200 199
 * Receives up to _maxlen_ bytes from +udpsocket+ using recvfrom(2) after
201 200
 * O_NONBLOCK is set for the underlying file descriptor.
......
211 210
 * === Parameters
212 211
 * * +maxlen+ - the number of bytes to receive from the socket
213 212
 * * +flags+ - zero or more of the +MSG_+ options
213
 * * +options+ - keyword hash, supporting `exception: false`
214 214
 *
215 215
 * === Example
216 216
 * 	require 'socket'
......
238 238
 * it is extended by IO::WaitReadable.
239 239
 * So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
240 240
 *
241
 * By specifying `exception: false`, the options hash allows you to indicate
242
 * that recvmsg_nonblock should not raise an IO::WaitWritable exception, but
243
 * return the symbol :wait_writable instead.
244
 *
241 245
 * === See
242 246
 * * Socket#recvfrom
243 247
 */
test/socket/test_nonblock.rb
1 1
begin
2 2
  require "socket"
3 3
  require "io/nonblock"
4
  require "io/wait"
4 5
rescue LoadError
5 6
end
6 7

  
......
275 276
    }
276 277
  end
277 278

  
279
  def test_recvfrom_nonblock_no_exception
280
    udp_pair do |s1, s2|
281
      assert_equal :wait_readable, s1.recvfrom_nonblock(100, exception: false)
282
      s2.send("aaa", 0, s1.getsockname)
283
      assert s1.wait_readable
284
      mesg, inet_addr = s1.recvfrom_nonblock(100, exception: false)
285
      assert_equal(4, inet_addr.length)
286
      assert_equal("aaa", mesg)
287
    end
288
  end
289

  
278 290
  if defined?(UNIXSocket) && defined?(Socket::SOCK_SEQPACKET)
279 291
    def test_sendmsg_nonblock_seqpacket
280 292
      buf = '*' * 10000
......
286 298
    rescue NotImplementedError, Errno::ENOSYS, Errno::EPROTONOSUPPORT
287 299
      skip "UNIXSocket.pair(:SEQPACKET) not implemented on this platform: #{$!}"
288 300
    end
301

  
302
    def test_sendmsg_nonblock_no_exception
303
      buf = '*' * 128
304
      UNIXSocket.pair(:SEQPACKET) do |s1, s2|
305
        n = 0
306
        Timeout.timeout(60) do
307
          case rv = s1.sendmsg_nonblock(buf, exception: false)
308
          when Integer
309
            n += rv
310
          when :wait_writable
311
            break
312
          else
313
            flunk "unexpected return value: #{rv.inspect}"
314
          end while true
315
          assert_equal :wait_writable, rv
316
          assert_operator n, :>, 0
317
        end
318
      end
319
    rescue NotImplementedError, Errno::ENOSYS, Errno::EPROTONOSUPPORT
320
      skip "UNIXSocket.pair(:SEQPACKET) not implemented on this platform: #{$!}"
321
    end
289 322
  end
290 323

  
291 324
  def test_recvmsg_nonblock_error
......
297 330
      rescue Errno::EWOULDBLOCK
298 331
        assert_kind_of(IO::WaitReadable, $!)
299 332
      end
333
      assert_equal :wait_readable, s1.recvmsg_nonblock(11, exception: false)
300 334
    }
301 335
  end
302 336

  
......
310 344
    }
311 345
  end
312 346

  
347
  def test_recv_nonblock_no_exception
348
    tcp_pair {|c, s|
349
      assert_equal :wait_readable, c.recv_nonblock(11, exception: false)
350
      s.write('HI')
351
      assert c.wait_readable
352
      assert_equal 'HI', c.recv_nonblock(11, exception: false)
353
      assert_equal :wait_readable, c.recv_nonblock(11, exception: false)
354
    }
355
  end
356

  
313 357
  def test_connect_nonblock_error
314 358
    serv = TCPServer.new("127.0.0.1", 0)
315 359
    _, port, _, _ = serv.addr
316
-