LoginSignup
0
0

More than 5 years have passed since last update.

fork は滅びぬ!何度でも蘇るさ!

Posted at

本当は fork で作られた子プロセスはちゃんと死んでくれます。

きっかけ

Fukuoka.rb #112以前と同様にもくもくとなるほど Unix プロセス ― Ruby で学ぶ Unix の基礎を読み進めていたら、 fork の章で挙動がよく分からない Errorno::EIO に遭遇した。

動作環境はこんな感じ。

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

事象

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

sample_fork.rb
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 さんに聞いてみた。

  1. fork を実行した時点で子プロセスができるが、irb を Ctrl+D で終了させても作成された子プロセスが生きている
  2. その後キーボードからの入力が、先ほど作成した子プロセスとターミナルの 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 で実行されることを想定していないのでは?」と指摘された。薄々そんな気はしてた

まとめ

サンプルソースは著者の気持ちを考えて実行しよう。

0
0
0

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