Elixirのプログラムを書いているときに。どういう風にSupervisorを設定したらいいかははまっていました。メモ程度で記録します。
今回やりたいのはSupervisorを起動すると、Stashが起動されます。そして、StashからSupervisorの下にworker supervisorを起動するようにします。Worker SupervisorあるいはStashを殺すと、両方再起動されます。workerを無視しても大丈夫です。このような構成はlearnyousomeerlang(ppoolの例)とElixir in Action二つの本に両方ありました。実際に自分で書いてみると、再起動ポリシーrestart
, one_for_one
, one_for_all
の部分でハマっていました。
:observer.start
は下のように表示してくれています。

$ mix new supervisor --sup --module SupervisorTest
supervisortest.ex
defmodule SupervisorTest do
use Application
require Logger
def start(_type, _args) do
import Supervisor.Spec, warn: false
# ここは:one_for_oneだと,しかもStashが死ぬと,エラーになる
opts = [strategy: :one_for_all, name: SupervisorTest.Supervisor]
children = [
# Stashの中からworker supervisorを起動したいので、ここのchildrenの中にworker supervisorを入れない
worker(SupervisorTest.Stash, [])
]
{:ok, sup}=Supervisor.start_link(children, opts)
Logger.info "Supervisor PID = #{inspect sup}"
{:ok, sup}
end
def stop(_app) do
:ok
end
end
stash.ex
defmodule SupervisorTest.Stash do
require Logger
use GenServer
def start_link() do
{:ok, pid} = GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
:ok = Logger.info "Stash PID = #{inspect pid}"
{:ok, pid} # 戻り値必要
end
def init(:ok) do
# Stashがすぐ戻り値を返せるように
# {:start_worker_sup}メッセージは最初に処理される
# なぜこの書き方はlearnyousomeerlangの18章参照
send self(), {:start_worker_sup}
{:ok, %{}}
end
def handle_info({:start_worker_sup}, state) do
import Supervisor.Spec
opts = [restart: :temporary]
{:ok, worker_sup_pid}= Supervisor.start_child(SupervisorTest.Supervisor, supervisor(SupervisorTest.WorkerSup, [], opts))
Process.link(worker_sup_pid)
{:noreply, state}
end
def handle_info(message, state) do
:ok = Logger.info"Unknown message #{inspect message}"
{:noreply, state}
end
end
workersup.ex
defmodule SupervisorTest.WorkerSup do
require Logger
use Supervisor
def start_link() do
{:ok, pid} = Supervisor.start_link(__MODULE__, :ok, name: __MODULE__)
Logger.info "Worker Supervisor PID = #{inspect pid}"
{:ok, pid}
end
def init(:ok) do
import Supervisor.Spec
opts = [strategy: :one_for_one, restart: :temporary]
supervise([], opts)
end
end
ここに一番重要なのstash.ex
ファイルにStashからworker supervisorを起動するときの設定
opts = [restart: :temporary]
{:ok, worker_sup_pid}= Supervisor.start_child(SupervisorTest.Supervisor, supervisor(SupervisorTest.WorkerSup, [], opts))
Process.link(worker_sup_pid)
Stashからworker supervisorを起動するときにrestart
をtemporary
に設定した上で、Process.link
関数を呼び出して、自分とworker supervisorをリンクするようにします。こうすることによって、worker supervisorが死ぬとStashも一緒に死にます。worker supervisorのrestart
はtemporary
なので、Supervisorはwoker supervisorを再起動しません。しかし、Stashもworker supervisorと一緒に死んだので、SupervisorはStashを再起動してくれます。Stashが再起動されるともう一回woker supervisorを起動します。これでプログラムは元の状態に戻ります。
もしProcess.link(worker_sup_pid)
を入れないと、shellの中で、worker supervisorを殺すコマンドProcess.exit(worker_sup_pid, :kill)
実行すると、worker supervisorが死ぬだけです。

しかしStashを殺すとStashとworker supervisorが両方死んで、SuperivosrがStashを起動してくれて、Stashがworker supervisorを起動します。。なぜ?supervisortest.ex
ファイルのopts = [strategy: :one_for_all, name: SupervisorTest.Supervisor]
にone_for_all
があるからです。もしここはone_for_one
だったら、Stashを殺すと
** (MatchError) no match of right hand side value: {:error, {:already_started, #PID<0.107.0>}}
のようなエラーがでます。なぜかというとStashだけが再起動され、worker supervisorは死んでないままなので、Stashをworker supervisorを起動しようとするけど、上のエラーがでます。
下はStashを殺して,Stashとworker supervisor両方再起動される例です.
19:05:38.835 [info] Stash PID = #PID<0.106.0> <----これを殺す
19:05:38.835 [info] Supervisor PID = #PID<0.105.0>
19:05:38.835 [info] Worker Supervisor PID = #PID<0.107.0>
Interactive Elixir (1.2.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> :observer.start
:ok
iex(2)> Process.exit(pid(0,106,0), :kill)
true
19:06:00.048 [info] Stash PID = #PID<0.138.0>
iex(3)>
19:06:00.048 [info] Worker Supervisor PID = #PID<0.139.0>
ドキュメントの抜粋
A temporary child process is never restarted (not even when the supervisor restart strategy is rest_for_one or one_for_all and a sibling death causes the temporary process to be terminated).
つまりSupervisorはone_for_all
設定されても(すべての子プロセスを再起動)temporary
と設定された
子プロセスに対して、効果なしです。