本当は fork
で作られた子プロセスはちゃんと死んでくれます。
きっかけ
Fukuoka.rb #112 で以前と同様にもくもくとなるほど Unix プロセス ― Ruby で学ぶ Unix の基礎を読み進めていたら、 fork
の章で挙動がよく分からない Errorno::EIO
に遭遇した。
動作環境はこんな感じ。
- Debian 9 (VirtualBox)
- Ruby 2.5.3
- irb 0.9.6
事象
先述の書籍に以下のような例があった。
if fork
puts "entered the if block"
else
puts "entered the else block"
end
この実行結果は以下のようになると記載してあった。
entered the if block
entered the else block
ソースファイルを作るのが面倒だったので irb 上で実行した。
$ irb
irb(main):001:0> if(fork); puts "entered the if block"; else; puts "entered the else block"; end
entered the if block
=> nil
irb(main):002:0> entered the else block
=> nil
irb(main):002:0>
$
ワンライナーで実行した後に、 Ctrl+D
で irb を終了させた。ここまでは理解できていたのだが、この後にキーボードから何かしらの入力を行うと以下のエラーが出力された。
$ irb
irb(main):001:0> if(fork); puts "entered the if block"; else; puts "entered the else block"; end
entered the if block
=> nil
irb(main):002:0> entered the else block
=> nil
irb(main):002:0>
$ Traceback (most recent call last):
21: from /home/jinroq/.rbenv/versions/2.5.3/bin/irb:11:in `<main>'
20: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:383:in `start'
19: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:427:in `run'
18: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:427:in `catch'
17: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:428:in `block in run'
16: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:487:in `eval_input'
15: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
14: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `catch'
13: from /home/jinroq/.rbenv/versions/2.5.3/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.3/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `loop'
11: from /home/jinroq/.rbenv/versions/2.5.3/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.3/lib/ruby/2.5.0/irb/ruby-lex.rb:266:in `lex'
9: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:292:in `token'
8: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/slex.rb:76:in `match'
7: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/slex.rb:206:in `match_io'
6: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:104:in `getc'
5: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:189:in `buf_input'
4: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:470:in `block in eval_input'
3: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:623:in `signal_status'
2: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:471:in `block (2 levels) in eval_input'
1: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/input-method.rb:151:in `gets'
/home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/input-method.rb:151:in `readline': Input/output error - read (Errno::EIO)
大体 readline が悪い…?
エラーを出しているのが readline なので、以前得た知見から irb --noreadline
だとエラーが出ないのではないかと考えた。
$ irb --noreadline
irb(main):001:0> if(fork); puts "entered the if block"; else; puts "entered the else block"; end
entered the if block
=> nil
irb(main):002:0> entered the else block
=> nil
irb(main):002:0>
$ Traceback (most recent call last):
21: from /home/jinroq/.rbenv/versions/2.5.3/bin/irb:11:in `<main>'
20: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:383:in `start'
19: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:427:in `run'
18: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:427:in `catch'
17: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:428:in `block in run'
16: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:487:in `eval_input'
15: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `each_top_level_statement'
14: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:231:in `catch'
13: from /home/jinroq/.rbenv/versions/2.5.3/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.3/lib/ruby/2.5.0/irb/ruby-lex.rb:232:in `loop'
11: from /home/jinroq/.rbenv/versions/2.5.3/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.3/lib/ruby/2.5.0/irb/ruby-lex.rb:266:in `lex'
9: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:292:in `token'
8: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/slex.rb:76:in `match'
7: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/slex.rb:206:in `match_io'
6: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:104:in `getc'
5: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/ruby-lex.rb:189:in `buf_input'
4: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:470:in `block in eval_input'
3: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:623:in `signal_status'
2: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb.rb:471:in `block (2 levels) in eval_input'
1: from /home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/input-method.rb:61:in `gets'
/home/jinroq/.rbenv/versions/2.5.3/lib/ruby/2.5.0/irb/input-method.rb:61:in `gets': Input/output error @ io_fillbuf - fd:0 (Errno::EIO)
事象は変わらずエラーメッセージを変えてきた。なんてお茶目なヤツなんだ。
分かりそうな人に聞いてみた
毎度おなじみ nagachika さんに聞いてみた。
-
fork
を実行した時点で子プロセスができるが、irb をCtrl+D
で終了させても作成された子プロセスが生きている - その後キーボードからの入力が、先ほど作成した子プロセスとターミナルの I/O とで競合しているため、件のエラーメッセージが出力される
確かめてみた
irb 上で fork させた直後に ps
コマンドで確認してみた。
jinroq 2096 0.1 0.5 48372 11372 pts/0 Sl+ 20:33 0:00 irb
jinroq 2127 0.0 0.4 48372 10044 pts/0 Sl+ 20:33 0:00 irb
おお!
irb を Ctrl+D
で終了させてみた。
jinroq 2127 0.0 0.4 48372 10044 pts/0 Sl+ 20:33 0:00 irb
pid:2096
がいなくなったので、親プロセスが pid:2096
、子プロセスが pid:2127
だったのだと思われる。
ちなみに
先述の実行例でお気づきの方もいるかもしれないが、 fork
実行例を実行した後の irb 行番号が変わっていない。
$ irb
irb(main):001:0> if(fork); puts "entered the if block"; else; puts "entered the else block"; end
entered the if block
=> nil
irb(main):002:0> entered the else block
=> nil
irb(main):002:0> <<<<<<<< ここ!
irb(main):002:0
が二回出力されている。これは…何だったっけな…。(だれか教えてください!!)
さらに irb 終了後にエラーメッセージを出さないようにするには else 文の最後に exit で明示的に終了させれば良い。
$ irb
irb(main):001:0> if(fork); puts "entered the if block"; else; puts "entered the else block"; exit; end
entered the if block
=> nil
irb(main):002:0> entered the else block
irb(main):003:0>
なお、nagachika さんからは「この書籍のサンプルは irb で実行されることを想定していないのでは?」と指摘された。薄々そんな気はしてた。
まとめ
サンプルソースは著者の気持ちを考えて実行しよう。