[ruby-dev:38140] IO::WantRead and IO::WantWrite module for nonblocking exceptions
From:
Tanaka Akira <akr@...>
Date:
2009-03-05 09:48:55 UTC
List:
ruby-dev #38140
IO::WantRead と IO::WantWrite というモジュールを作って、
EAGAIN や EWOULDBLOCK などが発生したときに、例外オブジェクト
に extend しておくのはどうでしょうか。
理由はふたつあります。
* rescue Errno::EAGAIN, Errno::EWOULDBLOCK と並べるのは面倒
nonblocking I/O でのエラーを rescue するときは、こうやって
ふたつ並べるのが行儀が良いとされていますが、ruby 側で、
read_nonblock で起きた EAGAIN と EWOULDBLOCK で
IO::WantRead を extend しておけば、
rescue IO::WantRead
というひとつ書けば済むようになります。
また、read_nonblock と write_nonblock では EAGAIN と
EWOULDBLOCK ですが、connect_nonblock では EINPROGRESS,
accept_nonblock では ... などという細かいことを覚えなくて
済むようになります。IO::WantRead か IO::WantWrite を
rescue して、それに対応して IO.select の read か write で
待てばいいわけです。
* openssl の read_nonblock の場合、read と write のどちらを
待つべきかわからない
1.9.2 では openssl に read_nonblock があり、実装としては
openssl の nonblocking I/O を使うわけですが、これは内部で
read(2) だけでなく write(2) を行う可能性があるようです。そ
して、write(2) がブロックしそうなときというのもありえるわ
けで、そのときにはブロックせずにエラーになるわけですが、そ
のときにアプリケーションは writable になるのを待ってやりな
おさなければなりません。
http://www.openssl.org/support/faq.html#PROG10
現在は read(2) でブロックしそうなときも write(2) でブロッ
クしそうなときも Errno::EWOULDBLOCK 例外が発生するので、
readable になるのを待つか writable になるのを待つかを例外
のクラスで区別することはできません。
read_nonblock で EWOULDBLOCK になったからといって readable
になるのを待つと、処理が進まなくなる可能性があります。
ここでどちらを待つべきかに応じて IO::WantRead と
IO::WantWrite を例外に extend しておけば、区別することが出
来ます。
なお、openssl のエラーは内部的には SSL_ERROR_WANT_READ と
SSL_ERROR_WANT_WRITE で、WantRead, WantWrite という名前は
ここに由来しています。
というわけでどうでしょうか。
% svn diff --diff-cmd diff -x '-u -p'
Index: include/ruby/ruby.h
===================================================================
--- include/ruby/ruby.h (revision 22781)
+++ include/ruby/ruby.h (working copy)
@@ -979,6 +979,7 @@ PRINTF_ARGS(NORETURN(void rb_raise(VALUE
PRINTF_ARGS(NORETURN(void rb_fatal(const char*, ...)), 1, 2);
PRINTF_ARGS(NORETURN(void rb_bug(const char*, ...)), 1, 2);
NORETURN(void rb_sys_fail(const char*));
+NORETURN(void rb_sys_fail_with_mod(VALUE, const char*));
NORETURN(void rb_iter_break(void));
NORETURN(void rb_exit(int));
NORETURN(void rb_notimplement(void));
@@ -1033,6 +1034,8 @@ RUBY_EXTERN VALUE rb_mFileTest;
RUBY_EXTERN VALUE rb_mGC;
RUBY_EXTERN VALUE rb_mMath;
RUBY_EXTERN VALUE rb_mProcess;
+RUBY_EXTERN VALUE rb_mWantRead;
+RUBY_EXTERN VALUE rb_mWantWrite;
RUBY_EXTERN VALUE rb_cBasicObject;
RUBY_EXTERN VALUE rb_cObject;
Index: io.c
===================================================================
--- io.c (revision 22781)
+++ io.c (working copy)
@@ -110,6 +110,8 @@ extern void Init_File(void);
VALUE rb_cIO;
VALUE rb_eEOFError;
VALUE rb_eIOError;
+VALUE rb_mWantRead;
+VALUE rb_mWantWrite;
VALUE rb_stdin, rb_stdout, rb_stderr;
VALUE rb_deferr; /* rescue VIM plugin */
@@ -1755,7 +1757,7 @@ io_getpartial(int argc, VALUE *argv, VAL
if (!nonblock && rb_io_wait_readable(fptr->fd))
goto again;
if (nonblock && errno == EWOULDBLOCK)
- rb_sys_fail("WANT_READ");
+ rb_sys_fail_with_mod(rb_mWantRead, "WANT_READ");
rb_sys_fail_path(fptr->pathv);
}
else if (n == 0) {
@@ -1956,7 +1958,7 @@ rb_io_write_nonblock(VALUE io, VALUE str
if (n == -1) {
if (errno == EWOULDBLOCK)
- rb_sys_fail("WANT_WRITE");
+ rb_sys_fail_with_mod(rb_mWantWrite, "WANT_WRITE");
rb_sys_fail_path(fptr->pathv);
}
@@ -8635,6 +8637,9 @@ Init_IO(void)
rb_cIO = rb_define_class("IO", rb_cObject);
rb_include_module(rb_cIO, rb_mEnumerable);
+ rb_mWantRead = rb_define_module_under(rb_cIO, "WantRead");
+ rb_mWantWrite = rb_define_module_under(rb_cIO, "WantWrite");
+
#if 0
/* This is necessary only for forcing rdoc handle File::open */
rb_define_singleton_method(rb_cFile, "open", rb_io_s_open, -1);
Index: ext/openssl/ossl_ssl.c
===================================================================
--- ext/openssl/ossl_ssl.c (revision 22781)
+++ ext/openssl/ossl_ssl.c (working copy)
@@ -1115,14 +1115,14 @@ ossl_ssl_read_internal(int argc, VALUE *
case SSL_ERROR_WANT_WRITE:
if (nonblock) {
errno = EWOULDBLOCK;
- rb_sys_fail("SSL_ERROR_WANT_WRITE");
+ rb_sys_fail_with_mod(rb_mWantWrite, "SSL_ERROR_WANT_WRITE");
}
rb_io_wait_writable(FPTR_TO_FD(fptr));
continue;
case SSL_ERROR_WANT_READ:
if (nonblock) {
errno = EWOULDBLOCK;
- rb_sys_fail("SSL_ERROR_WANT_READ");
+ rb_sys_fail_with_mod(rb_mWantRead, "SSL_ERROR_WANT_READ");
}
rb_io_wait_readable(FPTR_TO_FD(fptr));
continue;
Index: ext/socket/init.c
===================================================================
--- ext/socket/init.c (revision 22781)
+++ ext/socket/init.c (working copy)
@@ -200,7 +200,7 @@ rsock_s_recvfrom_nonblock(VALUE sock, in
#if defined(EWOULDBLOCK) && EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
#endif
- rb_sys_fail("recvfrom(2) WANT_READ");
+ rb_sys_fail_with_mod(rb_mWantRead, "recvfrom(2) WANT_READ");
}
rb_sys_fail("recvfrom(2)");
}
@@ -472,7 +472,7 @@ rsock_s_accept_nonblock(VALUE klass, rb_
#if defined EPROTO
case EPROTO:
#endif
- rb_sys_fail("accept(2) WANT_READ");
+ rb_sys_fail_with_mod(rb_mWantRead, "accept(2) WANT_READ");
}
rb_sys_fail("accept(2)");
}
Index: ext/socket/socket.c
===================================================================
--- ext/socket/socket.c (revision 22781)
+++ ext/socket/socket.c (working copy)
@@ -312,7 +312,7 @@ sock_connect_nonblock(VALUE sock, VALUE
n = connect(fptr->fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_LEN(addr));
if (n < 0) {
if (errno == EINPROGRESS)
- rb_sys_fail("connect(2) WANT_WRITE");
+ rb_sys_fail_with_mod(rb_mWantWrite, "connect(2) WANT_WRITE");
rb_sys_fail("connect(2)");
}
Index: ext/socket/ancdata.c
===================================================================
--- ext/socket/ancdata.c (revision 22781)
+++ ext/socket/ancdata.c (working copy)
@@ -1280,7 +1280,7 @@ bsock_sendmsg_internal(int argc, VALUE *
if (ss == -1) {
if (nonblock && errno == EWOULDBLOCK)
- rb_sys_fail("sendmsg(2) WANT_WRITE");
+ rb_sys_fail_with_mod(rb_mWantWrite, "sendmsg(2) WANT_WRITE");
rb_sys_fail("sendmsg(2)");
}
@@ -1565,7 +1565,7 @@ bsock_recvmsg_internal(int argc, VALUE *
if (ss == -1) {
if (nonblock && errno == EWOULDBLOCK)
- rb_sys_fail("recvmsg(2) WANT_READ");
+ rb_sys_fail_with_mod(rb_mWantRead, "recvmsg(2) WANT_READ");
#if defined(HAVE_ST_MSG_CONTROL)
if (!gc_done && (errno == EMFILE || errno == EMSGSIZE)) {
/*
Index: error.c
===================================================================
--- error.c (revision 22781)
+++ error.c (working copy)
@@ -1129,8 +1129,8 @@ rb_fatal(const char *fmt, ...)
rb_exc_fatal(rb_exc_new3(rb_eFatal, mesg));
}
-void
-rb_sys_fail(const char *mesg)
+static VALUE
+make_errno_exc(const char *mesg)
{
int n = errno;
VALUE arg;
@@ -1141,7 +1141,21 @@ rb_sys_fail(const char *mesg)
}
arg = mesg ? rb_str_new2(mesg) : Qnil;
- rb_exc_raise(rb_class_new_instance(1, &arg, get_syserr(n)));
+ return rb_class_new_instance(1, &arg, get_syserr(n));
+}
+
+void
+rb_sys_fail(const char *mesg)
+{
+ rb_exc_raise(make_errno_exc(mesg));
+}
+
+void
+rb_sys_fail_with_mod(VALUE mod, const char *mesg)
+{
+ VALUE exc = make_errno_exc(mesg);
+ rb_extend_object(exc, mod);
+ rb_exc_raise(exc);
}
void
Index: test/openssl/test_pair.rb
===================================================================
--- test/openssl/test_pair.rb (revision 22781)
+++ test/openssl/test_pair.rb (working copy)
@@ -147,7 +147,7 @@ class OpenSSL::TestPair < Test::Unit::Te
def test_read_nonblock
ssl_pair {|s1, s2|
err = nil
- assert_raise(Errno::EWOULDBLOCK) {
+ assert_raise(IO::WantRead) {
begin
s2.read_nonblock(10)
ensure
Index: test/openssl/test_ssl.rb
===================================================================
--- test/openssl/test_ssl.rb (revision 22781)
+++ test/openssl/test_ssl.rb (working copy)
@@ -172,12 +172,12 @@ class OpenSSL::TestSSL < Test::Unit::Tes
ssl = OpenSSL::SSL::SSLSocket.new(sock)
ssl.sync_close = true
ssl.connect
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { ssl.read_nonblock(100) }
+ assert_raise(IO::WantRead) { ssl.read_nonblock(100) }
ssl.write("abc\n")
IO.select [ssl]
assert_equal('a', ssl.read_nonblock(1))
assert_equal("bc\n", ssl.read_nonblock(100))
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { ssl.read_nonblock(100) }
+ assert_raise(IO::WantRead) { ssl.read_nonblock(100) }
}
end
Index: test/socket/test_nonblock.rb
===================================================================
--- test/socket/test_nonblock.rb (revision 22781)
+++ test/socket/test_nonblock.rb (working copy)
@@ -12,13 +12,13 @@ class TestSocketNonblock < Test::Unit::T
serv = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
serv.bind(Socket.sockaddr_in(0, "127.0.0.1"))
serv.listen(5)
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { serv.accept_nonblock }
+ assert_raise(IO::WantRead) { serv.accept_nonblock }
c = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
c.connect(serv.getsockname)
begin
s, sockaddr = serv.accept_nonblock
- rescue Errno::EWOULDBLOCK
- IO.select nil, [serv]
+ rescue IO::WantRead
+ IO.select [serv]
s, sockaddr = serv.accept_nonblock
end
assert_equal(Socket.unpack_sockaddr_in(c.getsockname), Socket.unpack_sockaddr_in(sockaddr))
@@ -57,8 +57,8 @@ class TestSocketNonblock < Test::Unit::T
u1 = UDPSocket.new
u2 = UDPSocket.new
u1.bind("127.0.0.1", 0)
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { u1.recvfrom_nonblock(100) }
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINVAL) { u2.recvfrom_nonblock(100) }
+ assert_raise(IO::WantRead) { u1.recvfrom_nonblock(100) }
+ assert_raise(IO::WantRead, Errno::EINVAL) { u2.recvfrom_nonblock(100) }
u2.send("aaa", 0, u1.getsockname)
IO.select [u1]
mesg, inet_addr = u1.recvfrom_nonblock(100)
@@ -67,7 +67,7 @@ class TestSocketNonblock < Test::Unit::T
af, port, host, addr = inet_addr
u2_port, u2_addr = Socket.unpack_sockaddr_in(u2.getsockname)
assert_equal(u2_port, port)
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { u1.recvfrom_nonblock(100) }
+ assert_raise(IO::WantRead) { u1.recvfrom_nonblock(100) }
u2.send("", 0, u1.getsockname)
assert_nothing_raised("cygwin 1.5.19 has a problem to send an empty UDP packet. [ruby-dev:28915]") {
timeout(1) { IO.select [u1] }
@@ -83,13 +83,13 @@ class TestSocketNonblock < Test::Unit::T
u1 = UDPSocket.new
u2 = UDPSocket.new
u1.bind("127.0.0.1", 0)
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { u1.recv_nonblock(100) }
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINVAL) { u2.recv_nonblock(100) }
+ assert_raise(IO::WantRead) { u1.recv_nonblock(100) }
+ assert_raise(IO::WantRead, Errno::EINVAL) { u2.recv_nonblock(100) }
u2.send("aaa", 0, u1.getsockname)
IO.select [u1]
mesg = u1.recv_nonblock(100)
assert_equal("aaa", mesg)
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { u1.recv_nonblock(100) }
+ assert_raise(IO::WantRead) { u1.recv_nonblock(100) }
u2.send("", 0, u1.getsockname)
assert_nothing_raised("cygwin 1.5.19 has a problem to send an empty UDP packet. [ruby-dev:28915]") {
timeout(1) { IO.select [u1] }
@@ -105,8 +105,8 @@ class TestSocketNonblock < Test::Unit::T
s1 = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
s1.bind(Socket.sockaddr_in(0, "127.0.0.1"))
s2 = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0)
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s1.recvfrom_nonblock(100) }
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINVAL) { s2.recvfrom_nonblock(100) }
+ assert_raise(IO::WantRead) { s1.recvfrom_nonblock(100) }
+ assert_raise(IO::WantRead, Errno::EINVAL) { s2.recvfrom_nonblock(100) }
s2.send("aaa", 0, s1.getsockname)
IO.select [s1]
mesg, sockaddr = s1.recvfrom_nonblock(100)
@@ -140,13 +140,13 @@ class TestSocketNonblock < Test::Unit::T
def test_tcp_recv_nonblock
c, s = tcp_pair
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { c.recv_nonblock(100) }
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.recv_nonblock(100) }
+ assert_raise(IO::WantRead) { c.recv_nonblock(100) }
+ assert_raise(IO::WantRead) { s.recv_nonblock(100) }
c.write("abc")
IO.select [s]
assert_equal("a", s.recv_nonblock(1))
assert_equal("bc", s.recv_nonblock(100))
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.recv_nonblock(100) }
+ assert_raise(IO::WantRead) { s.recv_nonblock(100) }
ensure
c.close if c
s.close if s
@@ -154,13 +154,13 @@ class TestSocketNonblock < Test::Unit::T
def test_read_nonblock
c, s = tcp_pair
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { c.read_nonblock(100) }
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.read_nonblock(100) }
+ assert_raise(IO::WantRead) { c.read_nonblock(100) }
+ assert_raise(IO::WantRead) { s.read_nonblock(100) }
c.write("abc")
IO.select [s]
assert_equal("a", s.read_nonblock(1))
assert_equal("bc", s.read_nonblock(100))
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) { s.read_nonblock(100) }
+ assert_raise(IO::WantRead) { s.read_nonblock(100) }
ensure
c.close if c
s.close if s
@@ -175,7 +175,7 @@ class TestSocketNonblock < Test::Unit::T
ret = c.write_nonblock(str)
assert_operator(ret, :>, 0)
loop {
- assert_raise(Errno::EAGAIN, Errno::EWOULDBLOCK) {
+ assert_raise(IO::WantWrite) {
loop {
ret = c.write_nonblock(str)
assert_operator(ret, :>, 0)
@@ -196,7 +196,7 @@ class TestSocketNonblock < Test::Unit::T
loop {
c.sendmsg_nonblock("a" * 100000)
}
- rescue Errno::EWOULDBLOCK
+ rescue IO::WantWrite
assert_match(/WANT_WRITE/, $!.message)
end
}
@@ -206,7 +206,7 @@ class TestSocketNonblock < Test::Unit::T
tcp_pair {|c, s|
begin
c.recvmsg_nonblock(4096)
- rescue Errno::EWOULDBLOCK
+ rescue IO::WantRead
assert_match(/WANT_READ/, $!.message)
end
}
@@ -216,7 +216,7 @@ class TestSocketNonblock < Test::Unit::T
tcp_pair {|c, s|
begin
c.recv_nonblock(4096)
- rescue Errno::EWOULDBLOCK
+ rescue IO::WantRead
assert_match(/WANT_READ/, $!.message)
end
}
@@ -243,7 +243,7 @@ class TestSocketNonblock < Test::Unit::T
port = serv.local_address.ip_port
begin
s, _ = serv.accept_nonblock
- rescue Errno::EWOULDBLOCK, Errno::ECONNABORTED, Errno::EPROTO
+ rescue IO::WantRead
assert_match(/WANT_READ/, $!.message)
end
ensure
Index: test/socket/test_addrinfo.rb
===================================================================
--- test/socket/test_addrinfo.rb (revision 22781)
+++ test/socket/test_addrinfo.rb (working copy)
@@ -236,7 +236,7 @@ class TestSocketAddrinfo < Test::Unit::T
c.connect(serv.local_address)
begin
ret = serv.accept_nonblock
- rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::ECONNABORTED, Errno::EPROTO, Errno::EINTR
+ rescue IO::WantRead, Errno::EINTR
IO.select([serv])
retry
end
@@ -299,7 +299,7 @@ class TestSocketAddrinfo < Test::Unit::T
s2.send("test-socket-recvfrom", 0, s1.getsockname)
begin
data, ai = s1.recvfrom_nonblock(100)
- rescue Errno::EWOULDBLOCK
+ rescue IO::WantRead
IO.select([s1])
retry
end
--
[田中 哲][たなか あきら][Tanaka Akira]