ElixirのSupervisorの設定

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](https://qiita-image-store.s3.amazonaws.com/0/54698/305a9bdd-cc75-6ca6-1f8c-3a6ce7b71972.png)

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

<img width="555" alt="スクリーンショット 2016-02-26 18.54.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/54698/1cd8635f-7920-6d4a-861e-37774c3a645c.png">


```
$ mix new supervisor --sup --module SupervisorTest
```

`supervisortest.ex`

```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`

```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`

```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が死ぬだけです。

<img width="526" alt="スクリーンショット 2016-02-26 18.56.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/54698/aa4876e2-955c-06cc-3dc8-9f2b1d2f209a.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両方再起動される例です．

```iex
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`と設定された
子プロセスに対して、効果なしです。