Project

General

Profile

Actions

Bug #3515

closed

FreeBSD wrongly raises ECONNRESET on close(2)

Added by naruse (Yui NARUSE) over 14 years ago. Updated over 13 years ago.

Status:
Closed
Target version:
ruby -v:
ruby 1.9.3dev (2010-07-02 trunk 28520) [x86_64-freebsd8.1]
Backport:
[ruby-dev:41778]

Description

=begin
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'


  1. 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&section=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

Related issues 1 (0 open1 closed)

Related to Ruby master - Bug #2632: Windows での未読のあるソケットのクローズ後の読み取りRejectedwanabe (_ wanabe)01/23/2010Actions
Actions #1

Updated by mame (Yusuke Endoh) over 14 years ago

  • Assignee set to naruse (Yui NARUSE)
  • Priority changed from Normal to 3
  • Target version set to 1.9.2

=begin
遠藤です。

2010年7月2日12:20 Yui NARUSE :

これらに共通するのは「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
=end

Actions #2

Updated by knu (Akinori MUSHA) over 14 years ago

=begin
これは意図されてドキュメントもされている変更なので、挙動としても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する方針のようだし、そういうライブラリはほかにも
多そうなので互換性を考慮して握りつぶすのは仕方ないと言えるでしょうか。
私は消極的ながら賛成です。(該当箇所にはコメントを入れてほしいです)
=end

Actions #3

Updated by naruse (Yui NARUSE) over 14 years ago

=begin
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
+

  •   * 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

     * 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:

=end

Actions #4

Updated by akr (Akira Tanaka) over 14 years ago

=begin
2010年7月2日12:20 Yui NARUSE :

この例外は socket の close(2) を呼んだ際に errno に ECONNRESET がセットされたときに発生します。
しかし、この挙動は POSIX 仕様外であり、FreeBSD 独自のものです。

ちょっとした好奇心なんですが、close(fd) が ECONNRESET になった後、
その fd は close されているんでしょうか、それともされていないんでしょうか。

[田中 哲][たなか あきら][Tanaka Akira]

=end

Actions #5

Updated by taca (Takahiro Kambe) over 14 years ago

=begin
In message
on Tue, 6 Jul 2010 15:44:50 +0900,
Tanaka Akira wrote:

2010年7月2日12:20 Yui NARUSE :

この例外は socket の close(2) を呼んだ際に errno に ECONNRESET がセットされたときに発生します。^M
何が、ここに制御文字(Ctl-M)を入れてるのだろう?

しかし、この挙動は POSIX 仕様外であり、FreeBSD 独自のものです。

ちょっとした好奇心なんですが、close(fd) が ECONNRESET になった後、
その fd は close されているんでしょうか、それともされていないんでしょうか。
さらに好奇心なのですが、close(fd)がECONNRESETになる状況を確認できる
簡単なプログラムはないでしょうか?

--
神戸 隆博 (かんべ たかひろ) at 仕事場

=end

Actions #6

Updated by naruse (Yui NARUSE) over 14 years ago

=begin
成瀬です。

2010年7月6日15:44 Tanaka Akira :

2010年7月2日12:20 Yui NARUSE :

この例外は 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

=end

Actions #7

Updated by naruse (Yui NARUSE) over 14 years ago

=begin
成瀬です。

2010年7月6日15:50 Takahiro Kambe :

さらに好奇心なのですが、close(fd)がECONNRESETになる状況を確認できる
簡単なプログラムはないでしょうか?

わたしは make TESTS='-v drb/test_drbssl.rb -n test_03' test-all で確認しています。
Ruby 側に手をいれつつ何度か走らせれば確認はできるかと思います。

--
NARUSE, Yui

=end

Actions #8

Updated by naruse (Yui NARUSE) over 14 years ago

  • Status changed from Open to Closed
  • % Done changed from 0 to 100

=begin
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.

=end

Actions #9

Updated by taca (Takahiro Kambe) over 14 years ago

=begin
In message
on Tue, 6 Jul 2010 18:02:26 +0900,
"NARUSE, Yui" wrote:

2010年7月6日15:50 Takahiro Kambe :

さらに好奇心なのですが、close(fd)がECONNRESETになる状況を確認できる
簡単なプログラムはないでしょうか?

わたしは make TESTS='-v drb/test_drbssl.rb -n test_03' test-all で確認しています。
Ruby 側に手をいれつつ何度か走らせれば確認はできるかと思います。
うーん、単体のCのプログラムで思ってました。;-(

--
神戸 隆博 / Takahiro Kambe

=end

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0