このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Erlang武士道プログラミング:死んだらどうなる

「潔く死ね」と言われても、死んだらどうなるか分かんないと不安だし、きれいな死に方もできないですよね。例外とプロセスの終了について、ざっと説明しましょう。

3種の例外の概要

Erlangの例外には、exit例外、throw例外、error例外があります(詳しくは「Erlang実験室:分かりにくいと評判のErlangエラーのまとめ」)。それぞれ、exit/1, erlang:error/1, throw/1 で発生させることができます。

exitはプロセス終了のために使う関数なので、エラーのとき闇雲にexitを使ってはいけません。

Richard Carlsson, "erlang:fault/1 vs. erlang:exit/1"
http://www.erlang.org/pipermail/erlang-questions/2006-May/020606.html

when you *really* do mean "terminate the process, giving Term as the reason if anyone is watching this process", use exit(Term).



「プロセスを終了させるよ。もし誰かこのプロセスを監視しているなら、Termが終了の理由だよ」ということを、ほんとうに伝えたいときだけ、exit(Term)を使いなさい。

となると、exit/1を使うケースのほとんどは、正常終了のときに exit(normal) とすることです。プロセス間通信のデータを、exitシグナルに乗せることもありますが、頻繁に使う手法ではありません。

throw例外は、キャッチされることを前提に投げる例外です。プロセスを終了させることを意図してません。throw例外は、親(呼び出し側)の関数、または祖先の関数でキャッチしてハンドルするのが原則です。つまり、不測の異常事態を意味するのではなくて、想定内のエラーにより正常な戻り値が作れなかったことを親/祖先に伝えるものです。

一方、error例外は、対処不可能な事故/災害として認識され、スタックトレースが取られてプロセスは終了します。多くの場合、ランタイムシステムが自動的に発行しますが、erlang:error/1 で人為的に発生させることもできます。error例外は原則としてキャッチしません*1。通常、キャッチしても何もできないからです(回復不能、打つ手なし)。

例外からシグナル

例外が発生すると、コールスタックのフレームを祖先方向へとたどって例外ハンドラを探します。見つかれば例外ハンドラに制御が移ります。見つからないとプロセスは終了します。そして、プロセスの終了に伴って終了シグナルが発生するのです。終了シグナルについては「Erlang実験室:例外のcatchと終了シグナル」を見てください。

発生したシグナルはどうなるかというと、誰もそのプロセスを監視してないと消えてしまいます。当該プロセスが死んだことも死んだ理由も誰にも知らされません、孤独死ってことです。しかし、たいていは複数プロセス間にリンクと呼ばれる関連づけがあって、リンクした他のプロセスが終了シグナルを受け止めます。

シグナルを受け取ると

他のプロセスの死を知らせるシグナルを受け取ると、デフォルトではシグナルを受信したプロセスも死んじゃいます。巻き添え、人身御供、殉死、一蓮托生と、いろいろな解釈があるでしょうが、ともかく一緒に死にます*2

process_flag(trap_exit, true)によって、終了シグナルを補足捕捉するように設定しておけば、自分は死なずに終了シグナルを受信できます。スーパーバイザプロセスとかモニタープロセスと呼ばれるプロセスは、このようにして他のプロセスの終了を監視して、シグナルに乗ってきたデータ(特にスタックトレースが重要)を記録したり、何らかの対処を行うのです。

誰を頼りにすればいいの?

他のプロセスとリンクせずに動いているプロセスは死んでも誰も何もしてくれないので、そのプロセス内で動く関数は安心して死ねません。終了シグナルを受け取り、適切に対処してくれる監視プロセスの体制が整っているからこそ「潔く死ぬ」とか言えるわけです。

監視と対処のフレームワークは、OTPライブラリに含まれるので、結論を言えば「OTP使え」ってことですな。

*1:無闇にキャッチして、再度erlang:error/1で投げ直すのはダメです。オリジナルのスタックトレースが消えてしまい、事故原因究明の大事な証拠が失われます。

*2:最初に死んだプロセスをクラッシャー(crusher)、巻き添えで死んだプロセス達を隣人(neighbors)と呼ぶようです。リンクされているプロセス全てがクラッシュの隣人となるわけではありません。trap_exitで保護されていれば大丈夫です。