ruby-changes:30616
From: tenderlove <ko1@a...>
Date: Tue, 27 Aug 2013 07:41:54 +0900 (JST)
Subject: [ruby-changes:30616] tenderlove:r42695 (trunk): * io.c (io_read_nonblock): support non-blocking reads without raising
tenderlove 2013-08-27 07:41:44 +0900 (Tue, 27 Aug 2013) New Revision: 42695 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=42695 Log: * io.c (io_read_nonblock): support non-blocking reads without raising exceptions. As in: `io.read_nonblock(size, exception: false)` [ruby-core:38666] [Feature #5138] * ext/openssl/ossl_ssl.c (ossl_ssl_read_internal): ditto * ext/stringio/stringio.c (strio_sysread): ditto * io.c (rb_io_write_nonblock): support non-blocking writes without raising an exception. * ext/openssl/ossl_ssl.c (ossl_ssl_write_internal): ditto * test/openssl/test_pair.rb (class OpenSSL): tests * test/ruby/test_io.rb (class TestIO): ditto * test/socket/test_nonblock.rb (class TestSocketNonblock): ditto * test/stringio/test_stringio.rb (class TestStringIO): ditto Modified files: trunk/ChangeLog trunk/ext/openssl/lib/openssl/buffering.rb trunk/ext/openssl/ossl_ssl.c trunk/ext/stringio/stringio.c trunk/io.c trunk/test/openssl/test_pair.rb trunk/test/ruby/test_io.rb trunk/test/socket/test_nonblock.rb trunk/test/stringio/test_stringio.rb Index: ChangeLog =================================================================== --- ChangeLog (revision 42694) +++ ChangeLog (revision 42695) @@ -1,3 +1,18 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Tue Aug 27 07:35:05 2013 Aaron Patterson <aaron@t...> + + * io.c (io_read_nonblock): support non-blocking reads without raising + exceptions. As in: `io.read_nonblock(size, exception: false)` + [ruby-core:38666] [Feature #5138] + * ext/openssl/ossl_ssl.c (ossl_ssl_read_internal): ditto + * ext/stringio/stringio.c (strio_sysread): ditto + * io.c (rb_io_write_nonblock): support non-blocking writes without + raising an exception. + * ext/openssl/ossl_ssl.c (ossl_ssl_write_internal): ditto + * test/openssl/test_pair.rb (class OpenSSL): tests + * test/ruby/test_io.rb (class TestIO): ditto + * test/socket/test_nonblock.rb (class TestSocketNonblock): ditto + * test/stringio/test_stringio.rb (class TestStringIO): ditto + Tue Aug 27 05:24:34 2013 Eric Hodel <drbrain@s...> * lib/rubygems: Import RubyGems 2.1.0 Release Candidate Index: io.c =================================================================== --- io.c (revision 42694) +++ io.c (revision 42695) @@ -159,7 +159,7 @@ static VALUE argf; https://github.com/ruby/ruby/blob/trunk/io.c#L159 static ID id_write, id_read, id_getc, id_flush, id_readpartial, id_set_encoding; static VALUE sym_mode, sym_perm, sym_extenc, sym_intenc, sym_encoding, sym_open_args; -static VALUE sym_textmode, sym_binmode, sym_autoclose; +static VALUE sym_textmode, sym_binmode, sym_autoclose, sym_exception; static VALUE sym_SET, sym_CUR, sym_END; #ifdef SEEK_DATA static VALUE sym_DATA; @@ -2413,14 +2413,14 @@ read_internal_call(VALUE arg) https://github.com/ruby/ruby/blob/trunk/io.c#L2413 } static VALUE -io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock) +io_getpartial(int argc, VALUE *argv, VALUE io, int nonblock, int no_exception) { rb_io_t *fptr; VALUE length, str; long n, len; struct read_internal_arg arg; - rb_scan_args(argc, argv, "11", &length, &str); + rb_scan_args(argc, argv, "11:", &length, &str, NULL); if ((len = NUM2LONG(length)) < 0) { rb_raise(rb_eArgError, "negative length %ld given", len); @@ -2452,8 +2452,12 @@ io_getpartial(int argc, VALUE *argv, VAL https://github.com/ruby/ruby/blob/trunk/io.c#L2452 if (n < 0) { if (!nonblock && rb_io_wait_readable(fptr->fd)) goto again; - if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) - rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "read would block"); + if (nonblock && (errno == EWOULDBLOCK || errno == EAGAIN)) { + if (no_exception) + return ID2SYM(rb_intern("wait_readable")); + else + rb_readwrite_sys_fail(RB_IO_WAIT_READABLE, "read would block"); + } rb_sys_fail_path(fptr->pathv); } } @@ -2529,7 +2533,7 @@ io_readpartial(int argc, VALUE *argv, VA https://github.com/ruby/ruby/blob/trunk/io.c#L2533 { VALUE ret; - ret = io_getpartial(argc, argv, io, 0); + ret = io_getpartial(argc, argv, io, 0, 0); if (NIL_P(ret)) rb_eof_error(); return ret; @@ -2590,16 +2594,62 @@ static VALUE https://github.com/ruby/ruby/blob/trunk/io.c#L2594 io_read_nonblock(int argc, VALUE *argv, VALUE io) { VALUE ret; + VALUE opts = Qnil; + int no_exception = 0; - ret = io_getpartial(argc, argv, io, 1); - if (NIL_P(ret)) - rb_eof_error(); + rb_scan_args(argc, argv, "11:", NULL, NULL, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + + ret = io_getpartial(argc, argv, io, 1, no_exception); + + if (NIL_P(ret)) { + if (no_exception) + return Qnil; + else + rb_eof_error(); + } return ret; } +static VALUE +io_write_nonblock(VALUE io, VALUE str, int no_exception) +{ + rb_io_t *fptr; + long n; + + if (!RB_TYPE_P(str, T_STRING)) + str = rb_obj_as_string(str); + + io = GetWriteIO(io); + GetOpenFile(io, fptr); + rb_io_check_writable(fptr); + + if (io_fflush(fptr) < 0) + rb_sys_fail(0); + + rb_io_set_nonblock(fptr); + n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); + + if (n == -1) { + if (errno == EWOULDBLOCK || errno == EAGAIN) { + if (no_exception) { + return ID2SYM(rb_intern("wait_writable")); + } else { + rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "write would block"); + } + } + rb_sys_fail_path(fptr->pathv); + } + + return LONG2FIX(n); +} + /* * call-seq: * ios.write_nonblock(string) -> integer + * ios.write_nonblock(string [, options]) -> integer * * Writes the given string to <em>ios</em> using * the write(2) system call after O_NONBLOCK is set for @@ -2648,34 +2698,25 @@ io_read_nonblock(int argc, VALUE *argv, https://github.com/ruby/ruby/blob/trunk/io.c#L2698 * according to the kind of the IO object. * In such cases, write_nonblock raises <code>Errno::EBADF</code>. * + * By specifying `exception: false`, the options hash allows you to indicate + * that write_nonblock should not raise an IO::WaitWritable exception, but + * return the symbol :wait_writable instead. + * */ static VALUE -rb_io_write_nonblock(VALUE io, VALUE str) +rb_io_write_nonblock(int argc, VALUE *argv, VALUE io) { - rb_io_t *fptr; - long n; - - if (!RB_TYPE_P(str, T_STRING)) - str = rb_obj_as_string(str); - - io = GetWriteIO(io); - GetOpenFile(io, fptr); - rb_io_check_writable(fptr); - - if (io_fflush(fptr) < 0) - rb_sys_fail(0); + VALUE str; + VALUE opts = Qnil; + int no_exceptions = 0; - rb_io_set_nonblock(fptr); - n = write(fptr->fd, RSTRING_PTR(str), RSTRING_LEN(str)); + rb_scan_args(argc, argv, "10:", &str, &opts); - if (n == -1) { - if (errno == EWOULDBLOCK || errno == EAGAIN) - rb_readwrite_sys_fail(RB_IO_WAIT_WRITABLE, "write would block"); - rb_sys_fail_path(fptr->pathv); - } + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exceptions = 1; - return LONG2FIX(n); + return io_write_nonblock(io, str, no_exceptions); } /* @@ -10809,7 +10850,7 @@ argf_getpartial(int argc, VALUE *argv, V https://github.com/ruby/ruby/blob/trunk/io.c#L10850 RUBY_METHOD_FUNC(0), Qnil, rb_eEOFError, (VALUE)0); } else { - tmp = io_getpartial(argc, argv, ARGF.current_file, nonblock); + tmp = io_getpartial(argc, argv, ARGF.current_file, nonblock, 0); } if (NIL_P(tmp)) { if (ARGF.next_p == -1) { @@ -11851,7 +11892,7 @@ Init_IO(void) https://github.com/ruby/ruby/blob/trunk/io.c#L11892 rb_define_method(rb_cIO, "readlines", rb_io_readlines, -1); rb_define_method(rb_cIO, "read_nonblock", io_read_nonblock, -1); - rb_define_method(rb_cIO, "write_nonblock", rb_io_write_nonblock, 1); + rb_define_method(rb_cIO, "write_nonblock", rb_io_write_nonblock, -1); rb_define_method(rb_cIO, "readpartial", io_readpartial, -1); rb_define_method(rb_cIO, "read", io_read, -1); rb_define_method(rb_cIO, "write", io_write_m, 1); @@ -12056,4 +12097,5 @@ Init_IO(void) https://github.com/ruby/ruby/blob/trunk/io.c#L12097 #ifdef SEEK_HOLE sym_HOLE = ID2SYM(rb_intern("HOLE")); #endif + sym_exception = ID2SYM(rb_intern("exception")); } Index: ext/openssl/ossl_ssl.c =================================================================== --- ext/openssl/ossl_ssl.c (revision 42694) +++ ext/openssl/ossl_ssl.c (revision 42695) @@ -103,6 +103,8 @@ static const char *ossl_ssl_attrs[] = { https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L103 ID ID_callback_state; +static VALUE sym_exception; + /* * SSLContext class */ @@ -1373,10 +1375,16 @@ ossl_ssl_read_internal(int argc, VALUE * https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1375 { SSL *ssl; int ilen, nread = 0; + int no_exception = 0; VALUE len, str; rb_io_t *fptr; + VALUE opts = Qnil; + + rb_scan_args(argc, argv, "11:", &len, &str, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; - rb_scan_args(argc, argv, "11", &len, &str); ilen = NUM2INT(len); if(NIL_P(str)) str = rb_str_new(0, ilen); else{ @@ -1397,17 +1405,23 @@ ossl_ssl_read_internal(int argc, VALUE * https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1405 case SSL_ERROR_NONE: goto end; case SSL_ERROR_ZERO_RETURN: + if (no_exception) { return Qnil; } rb_eof_error(); case SSL_ERROR_WANT_WRITE: + if (no_exception) { return ID2SYM(rb_intern("wait_writable")); } write_would_block(nonblock); rb_io_wait_writable(FPTR_TO_FD(fptr)); continue; case SSL_ERROR_WANT_READ: + if (no_exception) { return ID2SYM(rb_intern("wait_readable")); } read_would_block(nonblock); rb_io_wait_readable(FPTR_TO_FD(fptr)); continue; case SSL_ERROR_SYSCALL: - if(ERR_peek_error() == 0 && nread == 0) rb_eof_error(); + if(ERR_peek_error() == 0 && nread == 0) { + if (no_exception) { return Qnil; } + rb_eof_error(); + } rb_sys_fail(0); default: ossl_raise(eSSLError, "SSL_read"); @@ -1445,9 +1459,11 @@ ossl_ssl_read(int argc, VALUE *argv, VAL https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1459 * call-seq: * ssl.sysread_nonblock(length) => string * ssl.sysread_nonblock(length, buffer) => buffer + * ssl.sysread_nonblock(length[, buffer [, opts]) => buffer * * A non-blocking version of #sysread. Raises an SSLError if reading would - * block. + * block. If "exception: false" is passed, this method returns a symbol of + * :wait_writable, :wait_writable, or nil, rather than raising an exception. * * Reads +length+ bytes from the SSL connection. If a pre-allocated +buffer+ * is provided the data will be written into it. @@ -1459,7 +1475,7 @@ ossl_ssl_read_nonblock(int argc, VALUE * https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1475 } static VALUE -ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock) +ossl_ssl_write_internal(VALUE self, VALUE str, int nonblock, int no_exception) { SSL *ssl; int nwrite = 0; @@ -1476,10 +1492,12 @@ ossl_ssl_write_internal(VALUE self, VALU https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1492 case SSL_ERROR_NONE: goto end; case SSL_ERROR_WANT_WRITE: + if (no_exception) { return ID2SYM(rb_intern("wait_writable")); } write_would_block(nonblock); rb_io_wait_writable(FPTR_TO_FD(fptr)); continue; case SSL_ERROR_WANT_READ: + if (no_exception) { return ID2SYM(rb_intern("wait_readable")); } read_would_block(nonblock); rb_io_wait_readable(FPTR_TO_FD(fptr)); continue; @@ -1509,7 +1527,7 @@ ossl_ssl_write_internal(VALUE self, VALU https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1527 static VALUE ossl_ssl_write(VALUE self, VALUE str) { - return ossl_ssl_write_internal(self, str, 0); + return ossl_ssl_write_internal(self, str, 0, 0); } /* @@ -1520,9 +1538,18 @@ ossl_ssl_write(VALUE self, VALUE str) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L1538 * SSLError if writing would block. */ static VALUE -ossl_ssl_write_nonblock(VALUE self, VALUE str) +ossl_ssl_write_nonblock(int argc, VALUE *argv, VALUE self) { - return ossl_ssl_write_internal(self, str, 1); + VALUE str; + VALUE opts = Qnil; + int no_exception = 0; + + rb_scan_args(argc, argv, "1:", &str, &opts); + + if (!NIL_P(opts) && Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + + return ossl_ssl_write_internal(self, str, 1, no_exception); } /* @@ -2168,7 +2195,7 @@ Init_ossl_ssl() https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L2195 rb_define_method(cSSLSocket, "sysread", ossl_ssl_read, -1); rb_define_private_method(cSSLSocket, "sysread_nonblock", ossl_ssl_read_nonblock, -1); rb_define_method(cSSLSocket, "syswrite", ossl_ssl_write, 1); - rb_define_private_method(cSSLSocket, "syswrite_nonblock", ossl_ssl_write_nonblock, 1); + rb_define_private_method(cSSLSocket, "syswrite_nonblock", ossl_ssl_write_nonblock, -1); rb_define_method(cSSLSocket, "sysclose", ossl_ssl_close, 0); rb_define_method(cSSLSocket, "cert", ossl_ssl_get_cert, 0); rb_define_method(cSSLSocket, "peer_cert", ossl_ssl_get_peer_cert, 0); @@ -2239,4 +2266,6 @@ Init_ossl_ssl() https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L2266 ossl_ssl_def_const(OP_PKCS1_CHECK_2); ossl_ssl_def_const(OP_NETSCAPE_CA_DN_BUG); ossl_ssl_def_const(OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG); + + sym_exception = ID2SYM(rb_intern("exception")); } Index: ext/openssl/lib/openssl/buffering.rb =================================================================== --- ext/openssl/lib/openssl/buffering.rb (revision 42694) +++ ext/openssl/lib/openssl/buffering.rb (revision 42695) @@ -161,7 +161,7 @@ module OpenSSL::Buffering https://github.com/ruby/ruby/blob/trunk/ext/openssl/lib/openssl/buffering.rb#L161 # when the peer requests a new TLS/SSL handshake. See openssl the FAQ for # more details. http://www.openssl.org/support/faq.html - def read_nonblock(maxlen, buf=nil) + def read_nonblock(maxlen, buf=nil, exception: true) if maxlen == 0 if buf buf.clear @@ -171,7 +171,7 @@ module OpenSSL::Buffering https://github.com/ruby/ruby/blob/trunk/ext/openssl/lib/openssl/buffering.rb#L171 end end if @rbuffer.empty? - return sysread_nonblock(maxlen, buf) + return sysread_nonblock(maxlen, buf, exception: exception) end ret = consume_rbuff(maxlen) if buf @@ -370,9 +370,9 @@ module OpenSSL::Buffering https://github.com/ruby/ruby/blob/trunk/ext/openssl/lib/openssl/buffering.rb#L370 # is when the peer requests a new TLS/SSL handshake. See the openssl FAQ # for more details. http://www.openssl.org/support/faq.html - def write_nonblock(s) + def write_nonblock(s, exception: true) flush - syswrite_nonblock(s) + syswrite_nonblock(s, exception: exception) end ## Index: ext/stringio/stringio.c =================================================================== --- ext/stringio/stringio.c (revision 42694) +++ ext/stringio/stringio.c (revision 42695) @@ -119,6 +119,8 @@ typedef char strio_flags_check[(STRIO_RE https://github.com/ruby/ruby/blob/trunk/ext/stringio/stringio.c#L119 #define READABLE(strio) STRIO_MODE_SET_P(strio, READABLE) #define WRITABLE(strio) STRIO_MODE_SET_P(strio, WRITABLE) +static VALUE sym_exception; + static struct StringIO* readable(VALUE strio) { @@ -1327,7 +1329,6 @@ strio_read(int argc, VALUE *argv, VALUE https://github.com/ruby/ruby/blob/trunk/ext/stringio/stringio.c#L1329 * call-seq: * strio.sysread(integer[, outbuf]) -> string * strio.readpartial(integer[, outbuf]) -> string - * strio.read_nonblock(integer[, outbuf]) -> string * * Similar to #read, but raises +EOFError+ at end of string instead of * returning +nil+, as well as IO#sysread does. @@ -1342,8 +1343,50 @@ strio_sysread(int argc, VALUE *argv, VAL https://github.com/ruby/ruby/blob/trunk/ext/stringio/stringio.c#L1343 return val; } +/* + * call-seq: + * strio.read_nonblock(integer[, outbuf [, opts]]) -> string + * + * Similar to #read, but raises +EOFError+ at end of string unless the + * +exception: false+ option is passed in. + */ +static VALUE +strio_read_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE opts = Qnil; + int no_exception = 0; + + rb_scan_args(argc, argv, "11:", NULL, NULL, &opts); + + if (!NIL_P(opts)) { + argc--; + + if (Qfalse == rb_hash_aref(opts, sym_exception)) + no_exception = 1; + } + + VALUE val = strio_read(argc, argv, self); + if (NIL_P(val)) { + if (no_exception) + return Qnil; + else + rb_eof_error(); + } + + return val; +} + #define strio_syswrite rb_io_write +static VALUE +strio_syswrite_nonblock(int argc, VALUE *argv, VALUE self) +{ + VALUE str; + + rb_scan_args(argc, argv, "10:", &str, NULL); + return strio_syswrite(self, str); +} + #define strio_isatty strio_false #define strio_pid strio_nil @@ -1542,7 +1585,7 @@ Init_stringio() https://github.com/ruby/ruby/blob/trunk/ext/stringio/stringio.c#L1585 rb_define_method(mReadable, "readline", strio_readline, -1); rb_define_method(mReadable, "sysread", strio_sysread, -1); rb_define_method(mReadable, "readpartial", strio_sysread, -1); - rb_define_method(mReadable, "read_nonblock", strio_sysread, -1); + rb_define_method(mReadable, "read_nonblock", strio_read_nonblock, -1); rb_include_module(StringIO, mReadable); } { @@ -1552,7 +1595,9 @@ Init_stringio() https://github.com/ruby/ruby/blob/trunk/ext/stringio/stringio.c#L1595 rb_define_method(mWritable, "printf", strio_printf, -1); rb_define_method(mWritable, "puts", strio_puts, -1); rb_define_method(mWritable, "syswrite", strio_syswrite, 1); - rb_define_method(mWritable, "write_nonblock", strio_syswrite, 1); + rb_define_method(mWritable, "write_nonblock", strio_syswrite_nonblock, -1); rb_include_module(StringIO, mWritable); } + + sym_exception = ID2SYM(rb_intern("exception")); } Index: test/ruby/test_io.rb =================================================================== --- test/ruby/test_io.rb (revision 42694) +++ test/ruby/test_io.rb (revision 42695) @@ -1205,6 +1205,16 @@ class TestIO < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_io.rb#L1205 } end + def test_write_nonblock_simple_no_exceptions + skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + pipe(proc do |w| + w.write_nonblock('1', exception: false) + w.close + end, proc do |r| + assert_equal("1", r.read) + end) + end + def test_read_nonblock_error return if !have_nonblock? skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM @@ -1215,6 +1225,41 @@ class TestIO < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_io.rb#L1225 assert_kind_of(IO::WaitReadable, $!) end } + + with_pipe {|r, w| + begin + r.read_nonblock 4096, "" + rescue Errno::EWOULDBLOCK + assert_kind_of(IO::WaitReadable, $!) + end + } + end + + def test_read_nonblock_no_exceptions + return if !have_nonblock? + skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + with_pipe {|r, w| + assert_equal :wait_readable, r.read_nonblock(4096, exception: false) + w.puts "HI!" + assert_equal "HI!\n", r.read_nonblock(4096, exception: false) + w.close + assert_equal nil, r.read_nonblock(4096, exception: false) + } + end + + def test_read_nonblock_with_buffer_no_exceptions + return if !have_nonblock? + skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM + with_pipe {|r, w| + assert_equal :wait_readable, r.read_nonblock(4096, "", exception: false) + w.puts "HI!" + buf = "buf" + value = r.read_nonblock(4096, buf, exception: false) + assert_equal value, "HI!\n" + assert buf.equal?(value) + w.close + assert_equal nil, r.read_nonblock(4096, "", exception: false) + } end def test_write_nonblock_error @@ -1231,6 +1276,20 @@ class TestIO < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_io.rb#L1276 } end + def test_write_nonblock_no_exceptions + (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/