Elixirのプロセスは互いに独立していて、デフォルトでは何も共有しません。リンクされていない子プロセスがクラッシュしても、親プロセスは影響を受けません。
parent_pid =
spawn(fn ->
spawn(fn -> raise "oops" end)
receive do
message -> IO.inspect(message, label: "received message")
end
end)
# => 20:03:08.405 [error] Process #PID<0.153.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => true
この動作は、プロセス同士を_リンク_することで変えられます。2つのプロセスがリンクされていると、片方で起きた障害がもう片方にも伝播します。リンクは双方向です。
parent_pid =
spawn(fn ->
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
end
end)
# => 20:05:34.125 [error] Process #PID<0.171.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
Process.alive?(parent_pid)
# => false
プロセスは、spawn_link/1を使えば、呼び出し元のプロセスにリンクされた状態で生成できます。これはアトミックな操作です。あとからProcess.link/1でリンクすることもできます。
あるプロセスに現在リンクされている(まだ実行中の)プロセスは、Process.info/1で確認できます。
pid = spawn_link(fn -> :timer.sleep(50_000) end)
# => #PID<0.126.0>
self()
|> Process.info()
|> Keyword.get(:links)
# => [#PID<0.126.0>]
プロセスをリンクすると、並列化した処理を行うときに役立ちます。あるまとまりの処理が完了しなかったときに、ほかのまとまりの処理を続けるべきではない、という場面です。
リンクは、プロセスを_監視_するためにも使えます。プロセスが_exitをトラップ_していると、リンクされているプロセスがクラッシュしても、自分はクラッシュしません。代わりに、クラッシュに関するメッセージを受け取ります。これにより、たとえばクラッシュしたプロセスを再起動するなど、クラッシュに穏当に対処できます。
プロセスがexitをトラップするように設定するには、Process.flag(:trap_exit, true)を呼び出します。Process.flag/2が返すのは、フラグの_古い_値であり、新しい値ではないことに注意してください。
フラグの現在の値は、Process.info/1で確認できます。
pid
|> Process.info()
|> Keyword.get(:trap_exit)
# => false
リンクされているプロセスがクラッシュしたときにそのプロセスへ送られるメッセージは、{:EXIT, from, reason}というパターンにマッチします。ここでfromはPIDです。reasonがアトム:normal以外であれば、そのプロセスがクラッシュしたか、強制終了されたことを意味します。
parent_pid =
spawn(fn ->
Process.flag(:trap_exit, true)
spawn_link(fn -> raise "oops" end)
receive do
message ->
IO.inspect(message, label: "received message")
# do something else,
# to demonstrate that the process stays alive
:timer.sleep(50_000)
end
end)
# => 20:05:34.125 [error] Process #PID<0.295.0> raised an exception
# ** (RuntimeError) oops
# (stdlib 3.13.2) erl_eval.erl:678: :erl_eval.do_apply/6
# received message: {:EXIT, #PID<0.295.0>,
# {%RuntimeError{message: "oops"},
# [{:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 678]}]}}
Process.alive?(parent_pid)
# => true
exitをトラップすると、そのプロセスは、リンクされている_すべての_プロセスで起きたクラッシュの影響を受けなくなります。また、メールボックスにあるexitメッセージを読まないままでいると、メッセージがたまっていく可能性があります。そのため、exitをトラップするプロセスにリンクされているプロセスはすべて把握しておく必要があります。ふつう、exitをトラップするプロセスはほかのプロセスの監視だけを担当し、それ以外のことはしません。
実際には、監視用のプロセスを自分で一から実装することはないでしょう。Elixirには、こうした機能のための抽象化としてSupervisorビヘイビアが用意されています。
プロセスまわりのほかの抽象化、たとえばAgent、GenServer、Taskなどでは、起動方法が2つ用意されています。startかstart_linkです。