Edited at

ElixirのSupervisorの設定

More than 3 years have passed since last update.

Elixirのプログラムを書いているときに。どういう風にSupervisorを設定したらいいかははまっていました。メモ程度で記録します。

今回やりたいのはSupervisorを起動すると、Stashが起動されます。そして、StashからSupervisorの下にworker supervisorを起動するようにします。Worker SupervisorあるいはStashを殺すと、両方再起動されます。workerを無視しても大丈夫です。このような構成はlearnyousomeerlang(ppoolの例)とElixir in Action二つの本に両方ありました。実際に自分で書いてみると、再起動ポリシーrestart, one_for_one, one_for_allの部分でハマっていました。

スクリーンショット 2016-02-26 18.29.10.png

:observer.startは下のように表示してくれています。

スクリーンショット 2016-02-26 18.54.04.png

$ 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を起動するときにrestarttemporaryに設定した上で、Process.link関数を呼び出して、自分とworker supervisorをリンクするようにします。こうすることによって、worker supervisorが死ぬとStashも一緒に死にます。worker supervisorのrestarttemporaryなので、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が死ぬだけです。

スクリーンショット 2016-02-26 18.56.19.png

しかし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と設定された

子プロセスに対して、効果なしです。