From: SASADA Koichi Date: 2013-08-19T14:37:56+09:00 Subject: [ruby-dev:47638] Re: [ruby-trunk - Bug #8711] 最近NoMemoryErrorが多い (2013/08/01 20:18), naruse (Yui NARUSE) wrote: > http://u32.rubyci.org/~chkbuild/ruby-trunk/log/20130801T103302Z.log.html.gz > で 32bit でも安定したような気がします。 > > もうしばらく様子を見ます。 > > より多くのアドレス空間を必要とするようになった事自体は仕様って理解でいいんですよね>ささださん こちら、だいたいわかったんじゃないかなぁ、と思うので調査結果を報告します (日本語)。小崎さん、チェックしてくれると助かります。 簡単な報告: TestFiber#test_many_fibers が沢山仮想メモリを確保し、それを(なぜか)解 放しないため、その後の fork が ENOMEM で失敗します。 詳細な報告: 前提: 64bit OS で、2GB の物理メモリ+500MB の swap を持つシステム(VM)で検証 しています。Linux のバージョンは 3.2.0-51-generic。virtualbox 上で実行し ています。 (1) TestFiber#test_many_fibers が仮想メモリを沢山確保してしまう 今は、Fiber のスタックは mmap で確保しています。Fiber が GC されると munmap で解放するはずなんですが、何かの拍子にプロセスのアドレス空間が広 がったままになっているようです。これについては要検証。単に Fiber が GC されない、というか、GC されたけど、実際の解放は遅延している、という気が します。 Linux 側の RSS を見ると、1GB 弱しか使っていないことがわかりますが、仮想 メモリは 4GB ほど確保していることが観測できました。 Ruby 2.0 から、Fiber のためのスタックサイズが広がったため、この問題が生 じました(多分)。 (2) fork が ENOMEM で失敗 物理メモリ(+swap)以上の仮想メモリを持つプロセスを fork しようとする と、ENOMEM が起って失敗するようです。 (3) ENOMEM になってしまう理由 Linux は、(デフォルトでは)一度に物理メモリ(+α)以上の mmap は出来な いようになっているようです。 http://passingloop.tumblr.com/post/11957331420/overcommit-and-oom-killer > 0 > オーバーコミット有効 > 一回の malloc で確保できるのは実際に利用可能なメモリの大きさまで 多分、この制限で fork() が失敗してるんじゃないかなぁ、と思います。ちなみ に、このページの malloc って、mmap かなぁ。 Linux kernel のソースをおおざっぱに追ったのですが、fork() がこのチェック をするところにどう到達するかは見ることが出来ませんでした。ので、推測にな ります。 考えられる解決策: - TestFiber#test_many_fibers を削除 - TestFiber#test_many_fibers を別プロセスで実行 - TestFiber#test_many_fibers 後にちゃんと物理メモリを解放するように頑張る 余談: fork では、CoW になるから、ページテーブル操作だけ必要で、物理メモリの制 限はないだろー(fork 出来るだろう)、と思っていたんですが、そうでもない んですね。 しかし、この Linux の制限って少し不思議ですね。mmap(4GB) を行うと、(3) の条件で失敗しますが、mmap(2GB) を 2 回やると成功します。これだけだと連 続領域 4GB の確保が出来ないのかな、とも思いますが、実は MAP_WRITE を外す と mmap(4GB) は成功します。どうやら、書き込み可能な部分だけ、この(3) の チェックが入るようです。で、mprotect() で 2GB 単位でこの 4GB の連続領域 を書き込み可能にすることが可能です。つまり、 size = 4GB; addr = mmap(0, size, PROT_EXEC | PROT_READ, ...); mprotect(addr , size/2, 書き込み可能に); mprotect(addr+size/2, size/2, 書き込み可能に); とすると、書き込み可能な 4GB の連続領域を得ることが出来ます。もちろん、 実際に書き込んでいくとページが割り当てられてメモリ足りなくなりますが。 こういう設計なのは、なにがしかの理由があるんでしょうけれども、意外でした。 -- // SASADA Koichi at atdot dot net