LoginSignup
7
6

More than 5 years have passed since last update.

Phoenix で Supervisor の Worker を作ってアクセスしてみる

Posted at

Elixir の OTP まわりの復習として、Phoenix でプロジェクトを作成した時に作られる Supervisor ツリーに GenServer で作った Worker も入れてみて、動作を確認してみました。

最終的なソースコードは GitHub のphoenix-studies-genserverにあります。

Phoenix のファイル準備

特に特別なところはありません。brunch も ecto も必要ないので、

mix phoenix.new study --no-brunch --no-ecto

で生成したものを使っています(※テスト環境の4000ポートは埋まっているので、config/dev.exs の port は5000に変更してあります)。

Worker を GenServer で作成

lib/study/counter.ex として、下記のようなモジュールを作成しました。

get_state/0 を呼び出すと状態の値を、inc/0 はカウントを+1, また kill/0 は該当する handle_call/2 が無いため、パターンマッチエラーになることを期待しています。

defmodule Study.Counter do
  use GenServer

  def start_link(), do: start_link(0)

  def start_link(init_num) do
    GenServer.start_link(__MODULE__, %{last_message: "初期状態", count: init_num}, name: __MODULE__)
  end

  def get_state do
    GenServer.call(__MODULE__, :get_state)
  end

  def inc do
    GenServer.cast(__MODULE__, :inc)
  end

  def kill do
    GenServer.cast(__MODULE__, :kill)
  end


  ####################
  def handle_call(:get_state, _from, state) do
    {:reply, state, state}
  end

  def handle_cast(:inc, state) do
    new_state = %{count: state.count+1, last_message: "inc呼び出し"}
    {:noreply, new_state}
  end

end

動作テスト

iex -S mix で、作成した GenServer が正しく動くことを確認します。

iex(1)> Study.Counter.start_link
{:ok, #PID<0.175.0>}

iex(2)> Study.Counter.get_state
%{count: 0, last_message: "初期状態"}

iex(3)> Study.Counter.inc
:ok

iex(4)> Study.Counter.get_state
%{count: 1, last_message: "inc呼び出し"}

iex(5)> Study.Counter.inc
:ok

iex(6)> Study.Counter.get_state
%{count: 2, last_message: "inc呼び出し"}

iex(7)> Study.Counter.kill
** (EXIT from #PID<0.173.0>) an exception was raised:
    ** (FunctionClauseError) no function clause matching in Study.Counter.handle_cast/2
        (study) lib/study/counter.ex:28: Study.Counter.handle_cast(:kill, %{count: 2, last_message: "inc呼び出し"})
        (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
        (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
        (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3

Interactive Elixir (1.3.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> [error] GenServer Study.Counter terminating
** (FunctionClauseError) no function clause matching in Study.Counter.handle_cast/2
    (study) lib/study/counter.ex:28: Study.Counter.handle_cast(:kill, %{count: 2, last_message: "inc呼び出し"})
    (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", :kill}
State: %{count: 2, last_message: "inc呼び出し"}

nil
iex(2)> Study.Counter.get_state
** (exit) exited in: GenServer.call(Study.Counter, :get_state, 5000)
    ** (EXIT) no process
    (elixir) lib/gen_server.ex:596: GenServer.call/3

また、Study.Counter.kill がクラッシュすることや、クラッシュ後にプロセスが死んでいることも確認しています。

Supervisor の準備

作成した Worker をスーパーバイザ管理下にするため、lib/study.ex の children の worker の値を下記のように変更します。[0] はカウントの初期値です。

     children = [
        # Start the endpoint when the application starts
        supervisor(Study.Endpoint, []),
        # Start your own worker by calling: Study.Worker.start_link(arg1, arg2, arg3)
        # worker(Study.Worker, [arg1, arg2, arg3]),
        worker(Study.Counter, [0])  # ← この行を追加
      ]

Supervisor 配下の Worker の再起動を確認

Supervisor により、Worker がクラッシュしても再起動されることを確認します。再度 iex -S mix で試してみます。

最初の起動もスーパーバイザが自動的に行なってくれるため、Study.Counter.start_link/0 を実行すると :already_started でエラーになることに注意してください(つまり、start_link を実行する必要は無い、ということです)。

iex(1)> Study.Counter.start_link
{:error, {:already_started, #PID<0.225.0>}}

iex(2)> Study.Counter.get_state
%{count: 0, last_message: "初期状態"}

iex(3)> Study.Counter.inc
:ok

iex(4)> Study.Counter.get_state
%{count: 1, last_message: "inc呼び出し"}

iex(5)> Study.Counter.kill
:ok

iex(6)> [error] GenServer Study.Counter terminating
** (FunctionClauseError) no function clause matching in Study.Counter.handle_cast/2
    (study) lib/study/counter.ex:28: Study.Counter.handle_cast(:kill, %{count: 1, last_message: "inc呼び出し"})
    (stdlib) gen_server.erl:615: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:681: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", :kill}
State: %{count: 1, last_message: "inc呼び出し"}

nil
iex(7)> Study.Counter.get_state
%{count: 0, last_message: "初期状態"}

Phoenix から呼び出し

Worker の動作、スーパーバイザによる再起動の動作確認ができましたので、Phoenix から Worker を呼び出してみます。と言っても、コントローラで関数を呼び出して、テンプレートにセットしているだけですが。

router.ex に、Worker クラッシュ用の kill を追加します。

page_controller.ex
  scope "/", Study do
      pipe_through :browser # Use the default browser stack

      get "/", PageController, :index
      get "/kill", PageController, :kill
    end

controllers/page_controller.ex および templates/page/index.html.eex は以下です。

index では現在の状態を取得して inc/0 を呼び出しています。また kill はパターンマッチ該当無しでクラッシュさせて、/ にリダイレクトしています。

  defmodule Study.PageController do
    use Study.Web, :controller

    def index(conn, _) do
      params = Study.Counter.get_state
      call_inc(Study.Counter)

      render(conn, "index.html", params)
    end

    defp call_inc(process_name) do
      process_name.inc
    end


    def kill(conn, _) do
      Study.Counter.kill()
      url = page_path(conn, :index)
      redirect(conn, to: url)
    end

  end
index.html.eex
  <div class="jumbotron">
    <h2><%= gettext "Welcome to %{name}", name: "Phoenix!" %></h2>
    <p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p>
  </div>

  <div class="row marketing">
    <div class="col-lg-6">
      <h4>Resources</h4>
      <ul>
        <li> 最終メッセージ:<%= @last_message %>
        <li> カウント数:<%= @count %>
      </ul>
    </div>

  </div>

  <a href="<%= page_path(@conn, :kill) %>">/kill</a>

mix phoenix.server を実行して http://localhost:5000' に接続すると、Worker の状態の取得や inc/0 によるカウントが動くこと、またhttp://localhost:5000/kill` に接続すると Worker がクラッシュして再起動されることを確認できます。

非常に簡単ですね!

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6