OTP のスーパバイザを使ってみます。使ってるテキスト「プログラミングElixir」の17.1節がそのままでは動かないので、修正を試みます。テキストは Elixir 1.2 ベースで(1.2節による)、ここで動作させてる環境が Elixir 1.7.4 です。
なお、ここではテキストの内容を動かすための記載しかなくて、テキストの内容以上の新しい話はありません。
プログラミングElixir 17.1 の修正
17章が「OTP: スーパーバイザ」です。17.1節「スーパーバイザとワーカー」の210ページの先頭から異なってきます。以下、211ページまでを修正して動かしてみます。
投稿後に215ページについての修正を追加
mix new --sup sequence の結果
mix new --sup sequence
の lib の下の出力はテキストでは lib/sequence.ex になってますが、実際にはこの他に lib/sequence/application.ex が生成されます。
$ mix new --sup sequence
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/sequence.ex
* creating lib/sequence/application.ex
* creating test
* creating test/test_helper.exs
* creating test/sequence_test.exs
テキストで「だが、lib/sequence.ex を開いてみよう。」とある通りに lib/sequence.ex を見てもこんなのが出てきます。
defmodule Sequence do
@moduledoc """
Documentation for Sequence.
"""
@doc """
Hello world.
## Examples
iex> Sequence.hello()
:world
"""
def hello do
:world
end
end
ここは「だが、lib/sequence/application.ex を開いてみよう。」と思って見て下さい。
defmodule Sequence.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: Sequence.Worker.start_link(arg)
# {Sequence.Worker, arg},
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Sequence.Supervisor]
Supervisor.start_link(children, opts)
end
end
Sequence サーバのコピー
テキストには「lib/sequenceディレクトリを作り、」とありますが、mix の結果ですでに生成されています。
その後に「前章で作った Sequence.Server モジュールをその中にコピーする。」とあるのは、「前章で作った Sequence.Server モジュールを定義している server.ex を lib/sequence にコピーする。」と解釈して下さい。なお、server.ex を lib の下にコピーするのでも動きます。
アプリケーションの定義
アプリケーションモジュールを定義します。なおここで言う「アプリケーション」はテキスト18章先頭にあるように Erlang 用語で言うところの「アプリケーション」です。
テキストでは「そして、childrenリストにある…」とあるところはlib/sequence.ex でなく lib/sequence/application.ex に対する言及です。こちらのファイルを修正して下さい。
ここでは worker 関数を定義しています。これはテキスト同様にテンプレートを修正して下さい。
さらに import Supervisor.Spec
を入れて下さい。この import
文はテキストに寄るとテンプレートとして最初から入っていることになっていますが、なぜか入っておらず、手で入れないとなりません。
defmodule Sequence.Application do
use Application
def start(_type, _args) do
import Supervisor.Spec, warn: false # この行を追加する
children = [
worker(Sequence.Server, [123]) # この行を修正する
]
opts = [strategy: :one_for_one, name: Sequence.Supervisor]
{:ok, _pid} = Supervisor.start_link(children, opts)
end
end
実行
ここまでくるとあとはテキスト通りに試せます。
$ iex -S mix
Erlang/OTP 21 [erts-10.1.1] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]
Compiling 3 files (.ex)
Generated sequence app
Interactive Elixir (1.7.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Sequence.Server.increment_number 3
:ok
iex(2)> Sequence.Server.next_number
126
とここまでは順調で…
iex(3)> Sequence.Server.increment_number "cat"
:ok
iex(4)>
11:12:46.339 [error] GenServer Sequence.Server terminating
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(127, "cat")
(sequence) lib/sequence/server.ex:29: Sequence.Server.handle_cast/2
(stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:711: :gen_server.handle_msg/6
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: {:"$gen_cast", {:increment_number, "cat"}}
State: [data: [{'State', "My current state is '127', and I'm happy"}]]
iex(4)> Sequence.Server.next_number
123
iex(5)> Sequence.Server.next_number
124
と、強制的にプロセスを終了させるとスーパバイザがプロセスを再起動する様子が見て取れます。
stash サーバ付きの場合の修正
投稿後にテキスト見直してもう一箇所修正ありましたので追加します。215ページ一番下で sequence.ex を修正していますが、これは lib/sequence/application.ex です。
参考文献
プログラミングElixir
Elixir 1.7.3 Supervisor
謝辞
これは 2018.11.16(fri) に開催された fukuoka.ex#15:Elixir/Phoenixもくもく会~入門もあるよ(19:00開始) でもくもくした成果です。準備してくれたみなさま、ありがとうございました。