『9.8 キープアライブプロセス』徹底検証

『プログラミングErlang』138ページ。この1ページを理解するのに丸一日以上要したのは、単に私の頭が鈍いから、というだけではない!と思ったので、これから2回に分けて徹底的に検証していくことにする。

問題となる部分の引用:

on_exitとkeep_aliveにはちょっとした間違いがあるのに気付いただろうか?次のようなコードを書くと:

Pid = register(…),
on_exit(Pid, fun(X) -> ..),

この2つの文の間でプロセスが死んでしまう可能性がある。on_exitが評価される前にプロセスが死ぬとリンクは作られないので、on_exitプロセスは期待どおりの仕事をしてくれなくなる。そのような状況は2つのプロセスがNameに同じ値を指定して同時にkeep_aliveを評価しようとすると発生する。この状況は競合状態と呼ばれる。2つのコード(上記のコードと、on_exitの中でリンク操作を実行する部分)がお互いにリンク操作を取りあってしまう状態だ。ここで問題が起きると、プログラムは期待通りには動かないだろう。

問題点1. コードが間違っている

まず明らかに、文中のコードは間違っている。register/2 の戻り値は true なのだから、それを Pid という変数で受けて on_exit の引数に渡すのはおかしい。keep_alive 本来のコードは次のようになっている:

  register(Name, Pid = spawn(Fun)),
  on_exit(Pid, fun(Why) -> ..),

おそらく spawn と register の部分を次のように書き下そうとして間違えたのだろう。

  Pid = spawn(Fun),
  register(Name, Pid),
  on_exit(Pid, fun(Why) -> ..),

つまり作者がここで言わんとしているのは、「spawn してから link するまでにプロセスが死ぬ可能性がある」ということだと思われる。『9.2 on_exitハンドラ』のNoteや9.5の spawn_link の説明にも同趣旨のことが書いてある。

問題点2. on_exit は正常に動作しないが、keep_alive は期待通りに動く

さて、確かに spawn から on_exit(の中で呼び出されるlink)の間にプロセスが死ぬと on_exit は正しく動かなくなる。しかし keep_alive は期待通りに動いてしまう。つまりどのタイミングでプロセスが死んでも、必ず再起動される。

それを確かめるために次のような即座に終了する関数を keep_alive に与えてみよう。

F = fun() -> 1/0 end.

念を入れて spawn と on_exit の間に1秒のスリープを入れておく(私の環境では入れなくても結果は同じだった)。

keep_alive(Name, Fun) ->
    register(Name, Pid = spawn(Fun)),
    sleep(1000),
    on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).

これを実行してみれば、1秒おきに延々と再起動が繰り返されるのが確認できる(※)。

lib_misc:keep_alive(suicide, F).

なぜこのような動作になるかと言うと、システムプロセスが既に死んだプロセスに対して link すると、’noproc’ を原因として死んだかのような死亡通知メッセージが届くからだ(詳しくは以前の日記を参照)。

プロセスの本来の死因は link した時点では既に失われてしまっているから、on_exit 本来の機能としては不完全だろう。だから「on_exitプロセスは期待どおりの仕事をしてくれなくなる」という説明は間違ってはいない。しかし keep_alive に限って言えば、死因を問わず再起動するための関数なのだから、完全に期待通りに動く。というか動いてしまう。だからここで例として挙げるには相応しくないと言える(実際に実験してみて延々悩み続けた初心者がここに一人)。

続く。

※ spawn と register の間で競合が発生しなかった場合に限る。もしこの競合が発生した場合、 register の badargエラーで実行が停止する。詳しくは次回「問題点4」を参照。

One Response

  1. [...] へびにっき 樹上で暮らすヘビのように生きたい « 『9.8 キープアライブプロセス』徹底検証 [...]

Leave a Reply