Bug #3515
closedFreeBSD wrongly raises ECONNRESET on close(2)
Description
=begin
FreeBSD 8 では現在以下のようなテストに失敗しています。
-
Error:
test_idle(IMAPTest):
Errno::ECONNRESET: Connection reset by peer
/home/naruse/ruby/test/net/imap/test_imap.rb:189:in `test_idle' -
Failure:
test_03(TestDRbSSLCore) [/home/naruse/ruby/test/drb/drbtest.rb:138]:
[DRb::DRbConnError] exception expected, not
Class: Errno::ECONNRESET
Message: <"Connection reset by peer">
---Backtrace---
/home/naruse/ruby/test/drb/drbtest.rb:139:inblock in test_03' /home/naruse/ruby/test/drb/drbtest.rb:138:in
test_03'
- Failure:
test_07_public_private_protected_missing(TestDRbSSLCore) [/home/naruse/ruby/test/drb/drbtest.rb:182]:
Exception raised:
<#<Errno::ECONNRESET: Connection reset by peer>>.
これらに共通するのは「Errno::ECONNRESET: Connection reset by peer」という例外が発生している点です。
この例外は socket の close(2) を呼んだ際に errno に ECONNRESET がセットされたときに発生します。
しかし、この挙動は POSIX 仕様外であり、FreeBSD 独自のものです。
http://www.freebsd.org/cgi/man.cgi?query=close&apropos=0&sektion=0&manpath=FreeBSD+8.0-RELEASE&format=html
http://www.opengroup.org/onlinepubs/9699919799/functions/close.html
http://netbsd.gw.com/cgi-bin/man-cgi?close++NetBSD-current
http://www.openbsd.org/cgi-bin/man.cgi?query=close&apropos=0&sektion=0&manpath=OpenBSD+Current&arch=i386&format=html
http://leaf.dragonflybsd.org/cgi/web-man?command=close§ion=ANY
http://www.kernel.org/doc/man-pages/online/pages/man2/close.2.html
http://developer.apple.com/mac/library/documentation/Darwin/Reference/ManPages/man2/close.2.html
これが結果的に、他の OS では例外が投げられない状況で例外が発生するという現象を生み出しています。
以下は関連する議論です。
http://old.nabble.com/close()-failing-with-ECONNRESET-td28817716.html
http://old.nabble.com/Re:-kern-146845:--libc--close(2)-returns-error-54-(connection-reset-by-peer)-wrongly-td28649525.html
で、Ruby における対策ですが、close(2) で errno に ECONNRESET がセットされた場合、
それを無視するべきだと思います。
いかがそのパッチなのですがいかがでしょうか。
diff --git a/io.c b/io.c
index 05b2d45..a1b49d2 100644
--- a/io.c
+++ b/io.c
@@ -3436,7 +3436,7 @@ fptr_finalize(rb_io_t fptr, int noraise)
/ fptr->fd may be closed even if close fails.
* POSIX doesn't specify it.
* We assumes it is closed. */
-
if (close(fptr->fd) < 0 && NIL_P(err))
-
}if (close(fptr->fd) < 0 && NIL_P(err) && errno != ECONNRESET) err = noraise ? Qtrue : INT2NUM(errno);
skip_fd_close:
=end