Elixir で GenServer, Supervisor, mix deps.get で取得したパッケージの Application の起動順について調べたのでそのまとめ。
GenServer
GenServer.start_link/3
のドキュメントに次のように記述されています。
To ensure a synchronized start-up procedure, this function does not return until
init/1
has returned.
と、いうわけで。同じプロセスで複数の GenServer プロセスを起動するばあい、先に起動したプロセスの init/1
が完了してから後続のプロセスが起動することが保証できます。先に起動したプロセス宛に安全にメッセージを送信できると考えてよさそうです。
例
メッセージを受信する GenServer 。
defmodule Hoge.Foo do use GenServer require Logger def start_link(_), do: GenServer.start_link(__MODULE__, %{}, name: __MODULE__) def init(state), do: {:ok, state} def handle_cast({:foo, sender}, state) do Logger.info("#{__MODULE__} has received a message from #{sender}") {:noreply, state} end end
メッセージを送信する GenServer 。
defmodule Hoge.Bar do use GenServer def start_link(opts) do receiver = get_in(opts, [:receiver]) GenServer.start_link(__MODULE__, %{receiver: receiver}) end def init(state) do GenServer.cast(state.receiver, {:foo, __MODULE__}) {:ok, state} end end
メッセージを受信する GenServer と送信する GenServer を順に起動します。
defmodule Hoge do def do_something do {:ok, foo_pid} = Hoge.Foo.start_link([]) {:ok, bar_pid} = Hoge.Bar.start_link(receiver: foo_pid) end end
実行。
$ iex -S mix iex(1)> Hoge.do_something() 18:21:48.782 [info] Elixir.Hoge.Foo has received a message from Elixir.Hoge.Bar
この例ではほとんど処理時間がないためわかりにくいですが、Foo.init/1
で時間がかかる処理を記述してみても完了するまで Foo.start_link/1
は処理を待つので、Bar
は安全にメッセージを Foo
宛に送信することができます。
Supervisor
Supervisor
のドキュメントに次のように記述されています。
When the supervisor starts, it traverses all child specifications and then starts each child in the order they are defined.
また終了についても同じページの同じセクションに次のようにあります。
The shutdown process happens in reverse order.
監督する子プロセスの定義順に起動し、終了時はその逆順で停止されるようです。
例
Hoge
を次のように書き換えます。
defmodule Hoge do def do_something do children = [ Hoge.Foo, {Hoge.Bar, receiver: Hoge.Foo} ] opts = [strategy: :one_for_one, name: Hoge.Supervisor] Supervisor.start_link(children, opts) end end
Hoge.Foo
は名前付きで起動しているので GenServer.cast/2
は名前で呼び出すことができます。それを利用して、Hoge.Bar
には送信先を指定するパラメータに名前を渡しています。
実行。
$ iex -S mix iex(1)> Hoge.do_something 18:32:38.261 [info] Elixir.Hoge.Foo has received a message from Elixir.Hoge.Bar
特に代わり映えはしないです、はい。
mix deps.get で取得した Application
例えば次のように Foo
と Bar
がアプリケーションで、Hoge
がそれらを取り込んでなおかつ起動順を指定したいばあいです。
./ ├── bar/ │ └── lib/ │ ├── bar/ │ │ └── application.ex │ └── bar.ex ├── foo/ │ └── lib/ │ ├── foo/ │ │ └── application.ex │ └── foo.ex └── hoge/ ├── lib/ │ ├── hoge/ │ │ └── application.ex │ └── hoge.ex └── mix.exs
このようなばあいは、まず、 application/0
が返すパラメータに :included_application
を追加してそこにアプリケーション名を記述します。
アプリケーション名は mix.exs
の project/0
に :app
で記載しているパラメータの値になります。モジュール名でないので注意してください。
defmodule Hoge.MixProject do use Mix.Project def project do [ app: :hoge, version: "0.1.0", elixir: "~> 1.8", start_permanent: Mix.env() == :prod, deps: deps() ] end def application do [ extra_applications: [:logger], included_applications: [:foo, :bar], # 取り込むアプリケーションの名前を記述 mod: {Hoge.Application, []} ] end defp deps do [ {:foo, path: "../foo"}, {:bar, path: "../bar"} ] end end
ドキュメントにある通り :included_applications
に記載されたアプリケーションは自動的には起動しなくなります。
Any included application, defined in the
:included_applications
key of the.app
file will also be loaded, but they won't be started.
取り込んだ側のアプリケーションで Supervisor
などを利用して起動することで、アプリケーションの起動順を制御することができます。
たとえば。Foo.Application
と Bar.Application
を次のように定義しておくと、
defmodule Foo.Application do use Application def start(_type, _args) do children = [ Foo ] opts = [strategy: :one_for_one, name: Foo.Supervisor] Supervisor.start_link(children, opts) end end
defmodule Bar.Application do use Application def start(_type, args) do children = [ {Bar, args} ] opts = [strategy: :one_for_one, name: Bar.Supervisor] Supervisor.start_link(children, opts) end end
Hoge.Application
に次のように記述することで、順序を指定して起動させることができるようになります。
defmodule Hoge.Application do use Application def start(type, _args) do children = [ %{id: Foo.Application, start: {Foo.Application, :start, [type, nil]}}, %{id: Bar.Application, start: {Bar.Application, :start, [type, [receiver: Foo]]}} ] opts = [strategy: :one_for_one, name: Hoge.Supervisor] Supervisor.start_link(children, opts) end end
子プロセスを定義する記述は、 Supervisor.start_link/2
のエラーメッセージに書かれた説明が一番わかりやすそうです。
If you own the given module, please define a child_spec/1 function that receives an argument and returns a child specification as a map. For example: def child_spec(opts) do %{ id: __MODULE__, start: {__MODULE__, :start_link, [opts]}, type: :worker, restart: :permanent, shutdown: 500 } end Note that "use Agent", "use GenServer" and so on automatically define this function for you. However, if you don't own the given module and it doesn't implement child_spec/1, instead of passing the module name directly as a supervisor child, you will have to pass a child specification as a map: %{ id: Foo.Application, start: {Foo.Application, :start_link, [arg1, arg2]} }
メッセージにあるようにモジュールに child_spec/1
と定義するか、:id
と :start
を持つ Map を渡すかするようにします。
上記の例では Map を渡しています。
いつか読むはずっと読まない: Futabasaurus suzukii
国内で最も有名な首長竜にも関わらず正式な学名がついたのは 2006 年。 「かはく」こと 国立科学博物館の日本館三階北翼 をぜひ訪ねてみてください。
- 作者: 佐藤たまき,かわさきしゅんいち
- 出版社/メーカー: ブックマン社
- 発売日: 2018/08/03
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る