どうも、靖宗です。
前回に引き続き第4章。リンクとモニターどっちかで、今回はモニター使うんだからリンクしないようにしよう!みたいな流れになる気がします。
そもそも発行したプロセスを監視しないといけないのはプロセスが何らかの不具合でクラッシュしたりするのを監視して再起動したりする処理が書きたいがため。
他の言語ならtry/catch
の用に例外処理してもいいのかもしれませんが、Elixirでは基本的に推奨されてません。
そこで訳に立ってくるのがsupervisor
だそうです。その機能を見ていきます。
Our first supervisor
supervisor
の作成はGenServerとほぼ変わらないそうです。とりあえずサンプルを追っていきます。
supervisor
のために、lib/kv/supervisor.ex
にKV.Supervisor
を作成していきます。
defmodule KV.Supervisor do use Supervisor def start_link(opts) do Supervisor.start_link(__MODULE__, :ok, opts) end def init(:ok) do children = [ KV.Registry ] Supervisor.init(children, strategy: :one_for_one) end end
分かるような分からないような・・・
start_link/1
ではsupervisor
の起動。おそらく第一引数にコールバック関数があるモジュールの指定、第二引数に初期化関数への引数を渡す感じでしょうか。あと、必要に応じてオプション(opts
)。
初期化関数init/1
では、childrenというリストにKV.Registry
を含むリストを作成。おそらくこのリストにsupervisor
で管理したいプロセスのモジュールを書くのだとと思います。
そのあとでSupervisor.init/2
。第一引数は管理したいモジュールのリスト、第二引数は管理したモジュールがクラッシュした場合などの振る舞いのようです。
今回は:one_for_one
ということで、各プロセスを独立して管理する方針でしょうか。
また、use Agent
やuse GenServer
、use Supervisor
を使っている場合はモジュールにchild_spec/1
という関数が追加されます。一応iex.bat -S mix
で実行してみます。
PS > iex.bat -S mix Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> KV.Registry.child_spec([]) %{id: KV.Registry, start: {KV.Registry, :start_link, [[]]}}
なんかサンプルと違う・・・
とりあえず関数は追加されていて実行できるのでヨシとしましょう。
ちょっとまだ謎が多いですが、先に進みます。
Naming processes
GenServerで作成したレジストリもいちいちプロセスIDで管理せず名前を付けたいところです。
ここで、Bucketのとき同様アトムの使用を避けるのか?という発想になりかねませんが、このレジストリはBucketを束ねるものですので、本質的に1個で良いはずです。
ですので、この場合はアトムで命名していきます。
def init(:ok) do children = [ # KV.Registry {KV.Registry, name: KV.Registry} ] Supervisor.init(children, strategy: :one_for_one) end
{モジュール名, name: 名前}
のタプルになってます。モジュール名やん!と思われるかもしれませんが、Elixirでコンパイルをするときはモジュール名はアトムへ書き換えられるのでname:
の後は実質アトムと考えて問題無いと思います。
このchildren
の所に{モジュール名, name: 名前}
でモジュールを登録しておくと、そのモジュールのstart_link/1
の引数(opts
)に[name: 名前]
のキーワードリストが放り込まれて呼び出されるようです。
つまり、supervisorがKV.Registry
をKV.Registry.start_link([name: KV.Registry])
で呼び出し(プロセス発行し)ます。
ここまでできたらiexで実行してみます。iex.bat -S mix
で起動です。(もしかしたらmix compile
しといたほうがいいかも?)
PS > iex.bat -S mix Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> KV.Supervisor.start_link([]) {:ok, #PID<0.129.0>} iex(2)> KV.Registry.create(KV.Registry, "shopping") :ok iex(3)> KV.Registry.lookup(KV.Registry, "shopping") {:ok, #PID<0.133.0>}
KV.Registry
にBucketを作成できてるので、supervisor
を発行しただけでKV.Registry
が立ち上がっている事が分かります。
本来はKV.Registry
のプロセスIDを追っていかないとBucketの作成はできませんが、KV.Registry
というアトムで呼び出せるようになってるので自然に使えてます。この辺はsupervisor
で{KV.Registry, name: KV.Registry}
とした旨みでしょうか。
今回はiexで実行したので手動でsupervisor
を呼び出しましたが、アプリケーションのコールバックとして登録することで自動的に起動するよう設定するのが一般的だそうです。
その方法を追っていきます。
Understanding applications
コンパイルする度にGenerated kv app
とかでてましたが、まずはその本体を見てみましょう。
_build/dev/lib/kv/ebin/
にkv.app
があります。
{application,kv, [{applications,[kernel,stdlib,elixir,logger]}, {description,"kv"}, {modules,['Elixir.KV','Elixir.KV.Bucket','Elixir.KV.Registry', 'Elixir.KV.Supervisor']}, {registered,[]}, {vsn,"0.1.0"}, {extra_applications,[logger]}]}.
たぶん依存関係とかが書かれたファイルでしょう。
アプリケーションのバージョンやmix.exs
に書かれていた依存関係などが記載されています。
このファイルはErlangのVMで起動するのに使われてるのでしょうが、mixが良い感じに出力してくれるようです。
なので、この依存関係や最初にスタートするスクリプトの設定などはmix.exs
を編集すれば良いはずです。
mix.exs
のapplication/0
という関数を調整していくようです。
Starting applications
.app
ファイルに設定が書かれていればApplication
モジュールを通してアプリケーションの開始や停止を制御できるようです。
基本的にはiex -S mix
などで起動すれば自動でアプリケーションが立ち上がってますし、立ち上がって無くても自分で開始したり停止したりできます。
iex -S mix
で立ち上げて、起動していることを確認します。
iex> Application.start(:kv) {:error, {:already_started, :kv}}
デフォルトで:kv
が起動してます。(mix.exs
のproject/0
で定義したname
がついてる?)
iex -S mix run --no-start
でiex
を起動させればアプリケーションの起動をしないでiexが立ち上がるそうです。
PS > iex.bat -S mix run --no-start Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> Application.start(:kv) :ok
なるほど。
Application.stop/1
でアプリケーションの停止ができます。
iex> Application.stop(:kv) :ok iex> Application.stop(:logger) :ok
上では:logger
も止めましたが、:kv
だけ起動してみます。
iex> Application.start(:kv) {:error, {:not_started, :logger}}
:logger
起動しとらんやんけ!と怒られてます。きちんと依存関係が確認されているようです。
Application.ensure_all_started/1
を使えば、そのへんひっくるめて起動できるようです。
iex> Application.ensure_all_started(:kv) {:ok, [:logger, :kv]}
まぁ手動で起動することはあんまりないんじゃないでしょうか。
まだElixirで開発進めてないのでなんともいえませんが・・・
The application callback
Elixirのアプリケーションが起動した際に起こっている事を少々理解したところで、supervisor
の話に戻りましょう。
アプリケーションが起動した際に呼び出されるコールバック関数をモジュールに定義できるそうです。
start/2
という関数で定義しておき、返値に{:ok, pid}
が変えればいいようです。
まずは、コールバック関数があり呼び出しが必要である事をmix.exs
に追記します。
def application do [ extra_applications: [:logger], mod: {KV, []} # これ ] end
:mod
というオプションを追加するとKV
モジュールのstart/2
関数が呼び出されるようです。
ドキュメントを読んでもsupervisorに使用してるようなのでsupervisor使うときの必須設定みたいなもんでしょうか。
KV
モジュールにstart/2
を追記します。
defmodule KV do use Application def start(_type, _args) do KV.Supervisor.start_link(name: KV.Supervisor) end end
use Application
してるときは開始時のstart/2
と停止時のstop/1
をコールバック関数として実装できるようです。
今回はとりあえずstart/2
だけ。
実装できたら実際に起動してるか確認します。
KV.Supervisor.start_link([])
をした後でKV.Registry.create
してましたが、supervisorが起動してるならいきなりKV.Registry.create
してもいいはず。
PS > iex.bat -S mix Interactive Elixir (1.8.0) - press Ctrl+C to exit (type h() ENTER for help) iex(1)> KV.Registry.create(KV.Registry, "shopping") :ok iex(2)> KV.Registry.lookup(KV.Registry, "shopping") {:ok, #PID<0.133.0>}
できた!
この仕組みを理解してればいちいちSupervisorやGenServerの起動を意識して使用することは無さそうです。
イマイチ「OTPとは?」という回答にたどりつけてませんが、どうやらこのGenServerやSupervisorを使って機能を実現していくことあたりを指してる気がします。
まだまだ序盤なので先に進んでいきましょう。