LoginSignup
0
0

More than 5 years have passed since last update.

プロセスリソースに制限があろうとも僕は Process.#setrlimit で故意に落ちる

Posted at

皆さん、恋してますか?僕は故意に恋い焦がれて故意に泣きたい心境です。

きっかけ

Fukuoka.rb #109 でもくもくとなるほどUnixプロセス ― Rubyで学ぶUnixの基礎を読み進めていたのだが、 Process.#setrlimit の項目で意図せず irb が落ちた。なぜ落ちたのか自分にはさっぱり分からなかった。

動作環境はこんな感じ。

  • Debian 9 (VirtualBox)
  • Ruby 2.5.1
  • irb 0.9.6

事象

先述の書籍に以下のような実行例があった。

Process.setrlimit(:NOFILE, 3)
File.open('/dev/null')

これを実行すると Errno::EMFILE: Too many open files - /dev/null が出力されると書いてあったので、素直に irb で試してみた。

$ irb
irb(main):001:0> Process.setrlimit(:NOFILE, 3)
=> nil
Traceback (most recent call last):
    21: from /home/jinroq/.rbenv/versions/2.5.1/bin/irb:11:in `<main>'
    20: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:383:in `start'
    19: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:427:in `run'
    18: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:427:in `catch'
    17: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:428:in `block in run'
    16: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:487:in `eval_input'
    15: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
    14: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `catch'
    13: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
    12: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `loop'
    11: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:236:in `block (2 levels) in each_top_level_statement'
    10: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:266:in `lex'
     9: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:292:in `token'
     8: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/slex.rb:76:in `match'
     7: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/slex.rb:206:in `match_io'
     6: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:104:in `getc'
     5: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:189:in `buf_input'
     4: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:470:in `block in eval_input'
     3: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:623:in `signal_status'
     2: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:471:in `block (2 levels) in eval_input'
     1: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/input-method.rb:149:in `gets'
/home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/input-method.rb:149:in `input=': Invalid argument - dup (Errno::EINVAL)

$ 

Process.#setrlimit を実行しただけで irb が落ちた。それだけならまだしもエラーメッセージが Invalid argument と全く異なるものが出力された。

検証

Process.#setrlimitを見てみると何故かメソッドが 2 通り載っているが、これは
setrlimit(resource, cur_limit, max_limit = cur_limit)
ってことだろうか?ひとまず第二引数の値を色々いじって確認した。

$ irb
irb(main):001:0> Process.setrlimit(:NOFILE, 4)
=> nil
Traceback (most recent call last):
    21: from /home/jinroq/.rbenv/versions/2.5.1/bin/irb:11:in `<main>'
    20: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:383:in `start'
    19: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:427:in `run'
    18: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:427:in `catch'
    17: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:428:in `block in run'
    16: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:487:in `eval_input'
    15: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
    14: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `catch'
    13: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
    12: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `loop'
    11: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:236:in `block (2 levels) in each_top_level_statement'
    10: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:266:in `lex'
     9: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:292:in `token'
     8: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/slex.rb:76:in `match'
     7: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/slex.rb:206:in `match_io'
     6: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:104:in `getc'
     5: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:189:in `buf_input'
     4: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:470:in `block in eval_input'
     3: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:623:in `signal_status'
     2: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:471:in `block (2 levels) in eval_input'
     1: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/input-method.rb:149:in `gets'
/home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/input-method.rb:149:in `input=': Too many open files - dup (Errno::EMFILE)

$
$ irb
irb(main):007:0> Process.setrlimit(:NOFILE, 8)
=> nil
Traceback (most recent call last):
    21: from /home/jinroq/.rbenv/versions/2.5.1/bin/irb:11:in `<main>'
    20: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:383:in `start'
    19: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:427:in `run'
    18: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:427:in `catch'
    17: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:428:in `block in run'
    16: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:487:in `eval_input'
    15: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
    14: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `catch'
    13: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `block in each_top_level_statement'
    12: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `loop'
    11: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:236:in `block (2 levels) in each_top_level_statement'
    10: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:266:in `lex'
     9: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:292:in `token'
     8: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/slex.rb:76:in `match'
     7: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/slex.rb:206:in `match_io'
     6: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:104:in `getc'
     5: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/ruby-lex.rb:189:in `buf_input'
     4: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:470:in `block in eval_input'
     3: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:623:in `signal_status'
     2: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb.rb:471:in `block (2 levels) in eval_input'
     1: from /home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/input-method.rb:150:in `gets'
/home/jinroq/.rbenv/versions/2.5.1/lib/ruby/2.5.0/irb/input-method.rb:150:in `output=': Too many open files - dup (Errno::EMFILE)

$
$ irb
irb(main):001:0> Process.setrlimit(:NOFILE, 9)
=> nil
irb(main):002:0> p Process.getrlimit(:NOFILE)
[9, 9]
=> [9, 9]
irb(main):003:0> 

まとめるとこんな感じ。

cur_limit の値 実行結果
3 Invalid argument - dup (Errno::EINVAL)
4 以上 8 以下 Too many open files - dup (Errno::EMFILE)
9 以上 正常に制限値が設定される

ただし cur_limit = 8 の場合だけ、エラー箇所が input-method.rb:150 となっていた。

分かりそうな人に聞いてみた

という訳で近くにいた nagachika さんに聞いてみた。非常に親切丁寧に解説していただいたのだが、正直お話がディープすぎてあまり理解できていない。記憶を探って書いてみる。

  • 「irb が落ちている」のではなく、「irb が何かしらの理由で処理を停止させている」
  • irb の --noreadline オプションを付けると停止しないはず
  • irb 起動中のファイルディスクリプターの最小値は 3 ではなく、実は他にも動いている

確かめてみた

$ irb --noreadline
irb(main):001:0> Process.setrlimit(:NOFILE, 3)
=> nil
irb(main):002:0> File.open('/dev/null')
Traceback (most recent call last):
        4: from /home/jinroq/.rbenv/versions/2.5.1/bin/irb:11:in `<main>'
        3: from (irb):2
        2: from (irb):2:in `open'
        1: from (irb):2:in `initialize'
Errno::EMFILE (Too many open files @ rb_sysopen - /dev/null)
irb(main):003:0>

おお!

$ irb
irb(main):001:0> require 'objspace'
=> true
irb(main):002:0> ObjectSpace.each_object(IO).each{ |i| p i unless i.closed? }
#<IO:<STDERR>>
#<IO:<STDOUT>>
#<IO:<STDIN>>
#<IO:fd 1>
#<IO:fd 0>
=> 6
irb(main):003:0> 

おおー!

まとめ

  • readline を使わなければ期待通りに動く

おまけ

$ irb
irb(main):001:0> passwd = File.open('/etc/passwd')
=> #<File:/etc/passwd>
irb(main):002:0> puts passwd.fileno
9
=> nil
irb(main):003:0>

どうも 9 が最小値っぽい。また本の訳注には「ruby 1.9.3-p392/2.0.0-p0 では最小値 5。3 と 4 は YARV に予約されていた」とあった。つまり、

irb 起動中のファイルディスクリプター番号 予約している人
0, 1 fd
2 STDIN
3 STDOUT
4 STDERR
5, 6, 7 YARV

なんじゃないかな、と思った。(ツッコミ待ち)

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0