Help us understand the problem. What is going on with this article?

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした