[ruby-trunk - Bug #6441][Assigned] IO.pipe on ENFILE

Issue #6441 has been reported by naruse (Yui NARUSE).


Bug #6441: IO.pipe on ENFILE

Author: naruse (Yui NARUSE)
Status: Assigned
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category:
Target version:
ruby -v: ruby 2.0.0dev (2012-05-16 trunk 35659) [x86_64-netbsd6.99.5]

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。
これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

Issue #6441 has been updated by authorNari (Narihiro N.).

すみません、この問題をよく理解できてないので何点か質問させてください。
GC側をどのように直せばいいかいまいちわかっておらず、悩んでいます…。

naruse (Yui NARUSE) wrote:

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。

失敗する、というのは「fd の解放を試み、ダメなら諦める」ということが
IO.pipeでできないことがある、ということでしょうか?

これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

その例外というのはpipeで作られるfdがT_FILEのオブジェクトにわりあてられ
てないとかそういう話なのでしょうか。

また再現コードなどがあればいただけると嬉しいです。


Bug #6441: IO.pipe on ENFILE

Author: naruse (Yui NARUSE)
Status: Assigned
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category:
Target version:
ruby -v: ruby 2.0.0dev (2012-05-16 trunk 35659) [x86_64-netbsd6.99.5]

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。
これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

Issue #6441 has been updated by shyouhei (Shyouhei U.).

authorNari (Narihiro N.) wrote:

naruse (Yui NARUSE) wrote:

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。

失敗する、というのは「fd の解放を試み、ダメなら諦める」ということが
IO.pipeでできないことがある、ということでしょうか?

パイプがたくさんゴミになっている状況を考えてください。
本来であれば、GCされれば、それらのゴミパイプ(=fd)は回収されるはずです。

さて、そのような状況下であらたにIO.pipeを呼んだところ、ENFILEになったとしましょう。
この場合、fdが開放されることを期待してGCが走ります。
しかしながら現在のrubyではlazy sweepがあるため、GCしたからといってすぐにはsweepされません。
したがって、本来であればGCすることでゴミパイプが回収されてfdに余裕が出ることが期待されるところ、
引き続きfdが枯渇した状況が続いてしまいます(=GCしても意味なかった)。

これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

その例外というのはpipeで作られるfdがT_FILEのオブジェクトにわりあてられ
てないとかそういう話なのでしょうか。

上記のような問題がなぜパイプ以外で発生しないかというと、普通のファイルの場合だけlazy sweepにハックが入っていて、
「普通のファイルなら即座にsweep」ということになっているからです。という意味だと思われます。

また再現コードなどがあればいただけると嬉しいです。

zsh % ulimit -n 7 # 適当に小さい数字
zsh % ruby -ve ‘loop { IO.pipe }’
ruby 2.0.0dev (2012-09-16 trunk 36984) [x86_64-linux]
-e:1:in pipe': Too many open files (Errno::EMFILE) from -e:1:in block in ’
from -e:1:in loop' from -e:1:in
zsh: exit 1 ~/target/trunk/bin/ruby -ve ‘loop { IO.pipe }’

ゴミを生じているだけですので本来であれば無限に実行が続くべきです。

Bug #6441: IO.pipe on ENFILE

Author: naruse (Yui NARUSE)
Status: Assigned
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category:
Target version:
ruby -v: ruby 2.0.0dev (2012-05-16 trunk 35659) [x86_64-netbsd6.99.5]

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。
これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

Issue #6441 has been updated by nagachika (Tomoyuki C.).

どうも IO.pipe で最後に開いた IO 2つは GC.start で手動で GC を実行しても回収されないみたいです。

$ ruby -ve ‘GC.start;loop{IO.pipe.tap{|i|p i}.clear;GC.start}’"
ruby 2.0.0dev (2012-08-08 trunk 36663) [x86_64-darwin10.8.0]
[#<IO:fd 5>, #<IO:fd 6>]
[#<IO:fd 7>, #<IO:fd 8>]
[#<IO:fd 5>, #<IO:fd 6>]
[#<IO:fd 7>, #<IO:fd 8>]
[#<IO:fd 5>, #<IO:fd 6>]
[#<IO:fd 7>, #<IO:fd 8>]
…(以下繰り返し)
なので ulimit -n 9 だと動き続けることができるようでした。
はて、なんでしょう。

Bug #6441: IO.pipe on ENFILE

Author: naruse (Yui NARUSE)
Status: Assigned
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category:
Target version:
ruby -v: ruby 2.0.0dev (2012-05-16 trunk 35659) [x86_64-netbsd6.99.5]

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。
これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

Issue #6441 has been updated by nagachika (Tomoyuki C.).

どこから mark されてるんだろうかと追跡してみたところ
mark_current_machine_context() の rb_gc_mark_locations() から、つまり machine
stack からでした。

また optflags=-O0 をつけるとすぐに回収されていて ulimit -n 7
でも動き続けることができたので、少なくともわたしの手元の環境ではマシンスタックにゴミ参照が残っていて回収されないだけのようでした。参考まで。

Bug #6441: IO.pipe on ENFILE

Author: naruse (Yui NARUSE)
Status: Assigned
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category:
Target version:
ruby -v: ruby 2.0.0dev (2012-05-16 trunk 35659) [x86_64-netbsd6.99.5]

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。
これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

Issue #6441 has been updated by authorNari (Narihiro N.).

Status changed from Assigned to Rejected

私の環境でも調べて見ましたが、近永さんからご報告いただいたいのと同じ状況で、ゴミ参照が残っていて ulimit
で絞り過ぎると落ちるみたいでした。
これは仕方ないかなぁと思います。

追加の情報などがあればreopenをお願いします。

Bug #6441: IO.pipe on ENFILE

Author: naruse (Yui NARUSE)
Status: Rejected
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category:
Target version:
ruby -v: ruby 2.0.0dev (2012-05-16 trunk 35659) [x86_64-netbsd6.99.5]

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。
これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。

Issue #6441 has been updated by authorNari (Narihiro N.).

shyouhei (Shyouhei U.) wrote:

パイプがたくさんゴミになっている状況を考えてください。
本来であれば、GCされれば、それらのゴミパイプ(=fd)は回収されるはずです。

さて、そのような状況下であらたにIO.pipeを呼んだところ、ENFILEになったとしましょう。
この場合、fdが開放されることを期待してGCが走ります。
しかしながら現在のrubyではlazy sweepがあるため、GCしたからといってすぐにはsweepされません。
したがって、本来であればGCすることでゴミパイプが回収されてfdに余裕が出ることが期待されるところ、
引き続きfdが枯渇した状況が続いてしまいます(=GCしても意味なかった)。

ENFILEのときに呼び出されるrb_gc()はlazy sweepしないように作っていて、そ
ういうことが起きないようになっているはずなのですが、実際に問題が発生し
ているということは何かおかしそうですね。

zsh % ulimit -n 7 # 適当に小さい数字
zsh % ruby -ve ‘loop { IO.pipe }’
ruby 2.0.0dev (2012-09-16 trunk 36984) [x86_64-linux]
-e:1:in pipe': Too many open files (Errno::EMFILE) from -e:1:in block in ’
from -e:1:in loop' from -e:1:in
zsh: exit 1 ~/target/trunk/bin/ruby -ve ‘loop { IO.pipe }’

ゴミを生じているだけですので本来であれば無限に実行が続くべきです。

ありがとうございます。再現コードを基に調査してみます。


Bug #6441: IO.pipe on ENFILE

Author: naruse (Yui NARUSE)
Status: Assigned
Priority: Normal
Assignee: authorNari (Narihiro N.)
Category:
Target version:
ruby -v: ruby 2.0.0dev (2012-05-16 trunk 35659) [x86_64-netbsd6.99.5]

今の Ruby は open(2) などで、errno=ENFILE が発生した場合、
すなわち fd を使いきっている場合には、rb_gc() を呼び、IO オブジェクトを GC して、
fd が解放されないか試み、それでもダメだったら諦めるとしています。

しかし、IO.pipe の場合はこれに失敗することがあります。
これは、lazy sweep が上記の目的のため T_FILE の場合は直ちに sweep することにしている所、
pipe の場合はその例外にあたらないからです。