Bug #3515
FreeBSD wrongly raises ECONNRESET on close(2)
| Status: | Closed | Start date: | 07/02/2010 | |
|---|---|---|---|---|
| Priority: | Low | Due date: | ||
| Assignee: | % Done: | 100% |
||
| Category: | core | |||
| Target version: | 1.9.2 | |||
| ruby -v: | ruby 1.9.3dev (2010-07-02 trunk 28520) [x86_64-freebsd8.1] |
Description
FreeBSD 8 では現在以下のようなテストに失敗しています。
1) Error:
test_idle(IMAPTest):
Errno::ECONNRESET: Connection reset by peer
/home/naruse/ruby/test/net/imap/test_imap.rb:189:in `test_idle'
2) 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:in `block in test_03'
/home/naruse/ruby/test/drb/drbtest.rb:138:in `test_03'
---------------
3) 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:
Related issues
Associated revisions
* missing/close.c: ignore ECONNRESET.
FreeBSD wrongly sets ECONNRESET on close(2) and
it causes false-negative exceptions. [ruby-dev:41778]
* configure.in: ditto.
History
Updated by mame (Yusuke Endoh) almost 2 years ago
- Assignee set to naruse (Yui NARUSE)
- Priority changed from Normal to Low
- Target version set to 1.9.2
遠藤です。 2010年7月2日12:20 Yui NARUSE <redmine@ruby-lang.org>: > これらに共通するのは「Errno::ECONNRESET: Connection reset by peer」という例外が発生している点です。 > この例外は socket の close(2) を呼んだ際に errno に ECONNRESET がセットされたときに発生します。 > しかし、この挙動は POSIX 仕様外であり、FreeBSD 独自のものです。 > *snip* > > で、Ruby における対策ですが、close(2) で errno に ECONNRESET がセットされた場合、 > それを無視するべきだと思います。 FreeBSD が勝手に独自仕様に走っているのを、Ruby 側で吸収する「べき」とは 思いません。また、せっかく errno 設定しているのを、勝手に握りつぶしても よいものなんでしょうか。という疑問があるので、1.9.2 に入れるのはあまり 乗り気でないです。 ですが、最終的には FreeBSD のプラットフォームメンテナの判断に任せます。 この件で 1.9.2 のリリースを待つべきではありませんが、メンテナの判断が リリースに間に合えばコミットしてもいいかなと思います。ということで、 優先度を Low にします。 redmine の wiki に FreeBSD のプラットフォームメンテナが載っていないの ですが、成瀬さんで正しいでしょうか。 もし違ったら、FreeBSD は perhaps に降格となります。 -- Yusuke Endoh <mame@tsg.ne.jp>
Updated by knu (Akinori MUSHA) almost 2 years ago
これは意図されてドキュメントもされている変更なので、挙動としてもPOSIX的にもwrongではないです。 (SUSでは、ERRORSセクションで規定されている条件のいずれにも合致しないケースについて独自のエラーを 発生させることは許されているはず) 幾度かMLやIRCでも俎上には登っていますが、現実のコードとして、けっこうシビアなネットワークプログラムでも close(2)の返り値をチェックしないものが多く、portsとかcontribを見ても(FreeBSD用に)ECONNRESETの対応を 追加しているような例はほとんどありません。 その中で、OpenJDKではgetsockname(2)がFreeBSDでECONNRESETを返すというほぼ同じ問題に対して、同エラーを 握りつぶす処理を独自パッチで追加しています。これに倣うというのは確かにありかもしれません。 悩ましいのは、Rubyがほぼ一律にerrnoが発生したときはraiseするという慣習がある一方、上記のようにclose(2)の 失敗があまり検査されないという慣行を見た上で、それでも敢えてエラーを検出するプログラム向けにFYI的に ECONNRESETを教えてあげるというFreeBSDのこの挙動をどう扱うべきかというところですね。 DRbはSystemCallErrorについては自分で扱わずpass throughする方針のようだし、そういうライブラリはほかにも 多そうなので互換性を考慮して握りつぶすのは仕方ないと言えるでしょうか。 私は消極的ながら賛成です。(該当箇所にはコメントを入れてほしいです)
Updated by naruse (Yui NARUSE) almost 2 years ago
FreeBSD のメンテナは knu さんだと思っていました。 で、FreeBSDのこの挙動を知らない人には想像もつかない所で知らない例外が上がるため、 Ruby側としては吸収せざるをえないと思います。 例外のような処理を中断させてしまう方法以外で通知されるならまぁよかったのでしょうけど。 以下のような感じでtrunkにはコミットしようと思います。 1.9.2はどうしましょうか。 diff --git a/ChangeLog b/ChangeLog index 8698b39..8853a0b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +Mon Jul 5 18:33:17 2010 NARUSE, Yui <naruse@ruby-lang.org> + + * io.c (fptr_finalize): ignore ECONNRESET from close(2). + FreeBSD wrongly sets ECONNRESET on close(2) and it causes + false-negative exceptions. [ruby-dev:41778] + Mon Jul 5 12:32:01 2010 Aaron Patterson <aaron@tenderlovemaking.com> * ext/psych/lib/psych/scalar_scanner.rb (parse_string): support diff --git a/io.c b/io.c index 5129a14..4c817b2 100644 --- a/io.c +++ b/io.c @@ -3445,7 +3445,12 @@ 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) +#ifdef __FreeBSD__ + /* Ignore ECONNRESET of FreeBSD close(2) */ + && errno != ECONNRESET +#endif + ) err = noraise ? Qtrue : INT2NUM(errno); } skip_fd_close:
Updated by akr (Akira Tanaka) almost 2 years ago
2010年7月2日12:20 Yui NARUSE <redmine@ruby-lang.org>: > この例外は socket の close(2) を呼んだ際に errno に ECONNRESET がセットされたときに発生します。 > しかし、この挙動は POSIX 仕様外であり、FreeBSD 独自のものです。 ちょっとした好奇心なんですが、close(fd) が ECONNRESET になった後、 その fd は close されているんでしょうか、それともされていないんでしょうか。 -- [田中 哲][たなか あきら][Tanaka Akira]
Updated by taca (Takahiro Kambe) almost 2 years ago
In message <AANLkTimD2geIuuhr0GQZ4fprYTv3m4kuESajvsrxaItm@mail.gmail.com> on Tue, 6 Jul 2010 15:44:50 +0900, Tanaka Akira <akr@fsij.org> wrote: > 2010年7月2日12:20 Yui NARUSE <redmine@ruby-lang.org>: > >> この例外は socket の close(2) を呼んだ際に errno に ECONNRESET がセットされたときに発生します。^M 何が、ここに制御文字(Ctl-M)を入れてるのだろう? >> しかし、この挙動は POSIX 仕様外であり、FreeBSD 独自のものです。 > > ちょっとした好奇心なんですが、close(fd) が ECONNRESET になった後、 > その fd は close されているんでしょうか、それともされていないんでしょうか。 さらに好奇心なのですが、close(fd)がECONNRESETになる状況を確認できる 簡単なプログラムはないでしょうか? -- 神戸 隆博 (かんべ たかひろ) at 仕事場
Updated by naruse (Yui NARUSE) almost 2 years ago
成瀬です。 2010年7月6日15:44 Tanaka Akira <akr@fsij.org>: > 2010年7月2日12:20 Yui NARUSE <redmine@ruby-lang.org>: > >> この例外は socket の close(2) を呼んだ際に errno に ECONNRESET がセットされたときに発生します。 >> しかし、この挙動は POSIX 仕様外であり、FreeBSD 独自のものです。 > > ちょっとした好奇心なんですが、close(fd) が ECONNRESET になった後、 > その fd は close されているんでしょうか、それともされていないんでしょうか。 http://svn.freebsd.org/viewvc/base/head/lib/libstand/close.c?revision=165906&view=markup を見ると、呼んだ結果にかかわらず f->f_flags = 0; していますね。 -- NARUSE, Yui naruse@airemix.jp
Updated by naruse (Yui NARUSE) almost 2 years ago
成瀬です。 2010年7月6日15:50 Takahiro Kambe <taca@back-street.net>: > さらに好奇心なのですが、close(fd)がECONNRESETになる状況を確認できる > 簡単なプログラムはないでしょうか? わたしは make TESTS='-v drb/test_drbssl.rb -n test_03' test-all で確認しています。 Ruby 側に手をいれつつ何度か走らせれば確認はできるかと思います。 -- NARUSE, Yui naruse@airemix.jp
Updated by naruse (Yui NARUSE) almost 2 years ago
- Status changed from Open to Closed
- % Done changed from 0 to 100
This issue was solved with changeset r28561. Yui, thank you for reporting this issue. Your contribution to Ruby is greatly appreciated. May Ruby be with you.
Updated by taca (Takahiro Kambe) almost 2 years ago
In message <AANLkTilOo-wOIY7J1wkqY9ljTwLAzg0l6cQf3hB7qhI5@mail.gmail.com> on Tue, 6 Jul 2010 18:02:26 +0900, "NARUSE, Yui" <naruse@airemix.jp> wrote: > 2010年7月6日15:50 Takahiro Kambe <taca@back-street.net>: >> さらに好奇心なのですが、close(fd)がECONNRESETになる状況を確認できる >> 簡単なプログラムはないでしょうか? > > わたしは make TESTS='-v drb/test_drbssl.rb -n test_03' test-all で確認しています。 > Ruby 側に手をいれつつ何度か走らせれば確認はできるかと思います。 うーん、単体のCのプログラムで思ってました。;-( -- 神戸 隆博 / Takahiro Kambe