Elixir Concurrent Programming(3) - Supervisors

**■ ElixirのConcurrent Programmingについてまとめた過去記事**
[Elixir Concurrent Programming(1) - Spawn - Qiita](https://qiita.com/sand/items/d7fda56fe2fef0f4a24d)
[Elixir Concurrent Programming(2) - GenServer - Qiita](https://qiita.com/sand/items/5fd91c0b86b4919d7cc9)
[Elixir Concurrent Programming(3) - Supervisors - Qiita](https://qiita.com/sand/items/3075b909b65c2fa2fc23)
[Elixir Concurrent Programming(4) - Task and Agent　-Qiita](https://qiita.com/sand/items/244f060555d106a923d8)



　OTP supervisor behaviorは、worker processを監視し、停止したプロセスを再起動する機能を提供してくれます。

　以下、公式サイト[Supervisor behaviour](https://hexdocs.pm/elixir/Supervisor.html)で挙げられているコードを参考としつつ、Supervisorの機能を確認していきたいと思います。


#１．プロジェクト作成

　stackプロジェクトを作成します。


```
mix new --sup stack
```

　--supが付いているので、application.exというファイルが生成されます。このファイルについては後で説明します。


```
# mix new --sup stack
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/stack.ex
* creating lib/stack/application.ex ### supオプションでこれが作成される
* creating test
* creating test/test_helper.exs
* creating test/stack_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd stack
    mix test

Run "mix help" for more commands.
```


　このプロジェクトで動作するプロセスは以下の3個になります。SupervisorがBackupとServerを起動し、監視し、停止時に再起動します。このプロセスの関係はapplication.exで定義します。


```
Supervisor ------ Backup
              |
              |-- Server
```




#２．Stack.Server

　Stack.Serverは、Stackサービスを提供するGenServerです。initでStack.Backup.get()で初期化用のstateを取得します。terminate時にカレントのstateをStack.Backup.updateでバックアップします。強制的にプロセスを停止させるためにhandle_cast(:kill, stack)を実装しています。


```elixir:lib/stack/server.ex
defmodule Stack.Server do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, nil, name: __MODULE__)
  end

  def pop do
    GenServer.call __MODULE__, :pop
  end

  def push(head) do
    GenServer.cast __MODULE__, {:push, head}
  end


  ## Callbacks

  def init(_) do
    {:ok, Stack.Backup.get() }  ### 初期化時にBackupサーバを参照
  end

  def handle_call(:pop, _from, [head | tail]) do
    {:reply, head, tail}
  end

  def handle_cast({:push, head}, tail) do
    {:noreply, [head | tail]}
  end

  def handle_cast(:kill, stack) do  ### 強制的にプロセスを停止させる
    a = 1 / 0
    {:noreply, [stack]}
  end

  def terminate(_reason, current_stack) do
    Stack.Backup.update(current_stack)  ### 停止時にBackupサーバを更新
  end
end
```


#３．Stack.Backup

　Stack.Backup GenServerは、Stack.Serverがstackをバックアップするためのサーバです。Stack.Serverは初期値をStack.Backupから取得します。またStack.Serverは、停止時にStack.Backupにバックアップを取り、再起動時にそのバックアップを取得することで、停止時のstateのを復元します。

```elixir:lib/stack/backup.ex
defmodule Stack.Backup do
  use GenServer
  @me __MODULE__

  def start_link(initial_stack) do
    GenServer.start_link(__MODULE__, initial_stack, name: @me)
  end

  def get() do
    GenServer.call(@me, { :get })
  end

  def update(new_stack) do
    GenServer.cast(@me, { :update, new_stack })
  end


  # Server implementation

  def init(initial_stack) do
    { :ok, initial_stack }
  end

  def handle_call({ :get }, _from, current_stack ) do
    { :reply, current_stack, current_stack }
  end

  def handle_cast({ :update, new_stack }, _current_stack) do
    { :noreply, new_stack }
  end
end
```



#４．application.ex

　childrenに起動するプロセスをリストします。Stack.Backupには初期値を指定します。Stack.ServerはStack.Backupから初期値を得るので、初期値は指定しません。

　**:rest_for_one**戦略は次のようなものです。childrenリストのサーバが死んだときに、自分より後にリストされているサーバも停止する。その後で停止サーバを全て再起動する。その他の戦略は次の通りです。**:one_for_one** は単に死んだサーバを再起動するのみで、デフォルトです。**:one_for_all** はサーバが死んだら、他の全てのサーバも停止して、全て再起動する戦略です。


```elixir:lib/stack/application.ex
defmodule Stack.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, initial_stack) do
    # List all child processes to be supervised
    children = [
        { Stack.Backup, initial_stack},
        { Stack.Server, nil},
    ]

    opts = [strategy: :rest_for_one, name: Stack.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

　OTP Applicationの実装です。mix.exsにて本ApplicationのトップのmoduleがStackであることを教え、初期値を与えます。registeredオプションでStack.ServerやStack.Backupの名前のユニーク性(Node単位)を確保します。


```elixir:mix.exs
  # Run "mix help compile.app" to learn about applications.
  def application do
    [
      extra_applications: [:logger],
      mod: {Stack.Application, [[:hello]]},
      registered: [
        Stack.Server,
        Stack.Backup,
      ]
    ]
  end
```




#５．Supervisorの動作の確認

　以下、Stack.Serverがエラーを起こし停止します。自動的に再起動するのですが、停止した時のstateを保持しているのがわかります。後ろでStack.Backupが機能しているおかげです。


```
# iex -S mix
Generated stack app
iex(1)> Stack.Server.pop
[:hello]
iex(2)> Stack.Server.push(:world)
:ok
iex(3)> Stack.Server.push(:world2)
:ok
iex(4)> Stack.Server.push(:world3)
:ok
iex(5)>  GenServer.cast Stack.Server, :kill
:ok
iex(6)>
12:56:07.504 [error] GenServer Stack.Server terminating
** (ArithmeticError) bad argument in arithmetic expression
    (stack) lib/stack/server.ex:32: Stack.Server.handle_cast/2
    (stdlib) gen_server.erl:616: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:686: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", :kill}
State: [:world3, :world2, :world]

nil
iex(7)> Stack.Server.pop
:world3
iex(8)> Stack.Server.pop
:world2
```


　以上です




■ **OTP supervisor behavior関連の過去記事**
[東京電力電力供給状況監視 - Phoenix Channel - Qiita](https://qiita.com/sand/items/5aa0d8ff778854411a42)
[Phoenix Channelで作る最先端Webアプリ - Elixier Application編 - Qiita](https://qiita.com/sand/items/5ae4495154dacb4bea71)
[Phoenix Channelで作る最先端Webアプリ - DynamicSupervisor編 - Qiita](https://qiita.com/sand/items/c2f089e218a0e9878abd)