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

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

# きっかけ

[Fukuoka.rb #112](https://fukuokarb.connpass.com/event/104693/) で[以前と同様](https://qiita.com/oda-i3/items/16120416ad0267660ade)にもくもくと[なるほど Unix プロセス ― Ruby で学ぶ Unix の基礎](https://tatsu-zine.com/books/naruhounix)を読み進めていたら、 `fork` の章で挙動がよく分からない `Errorno::EIO` に遭遇した。

動作環境はこんな感じ。

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

# 事象

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

```ruby: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](https://twitter.com/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 で実行されることを想定していないのでは？」と指摘された。**薄々そんな気はしてた**。

# まとめ

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