simple_one_for_oneのメリット
Supervisor起動戦略
Supervisor の子プロセスに関する振舞いには
- one_for_one
- one_for_all
- rest_for_one
- simple_one_for_one
の 4 つがある.
Supervisorのプロセス生成時に監視対象のプロセスも生成する
1-3 はスーパーバイザー起動時に子プロセス生成のための情報を渡しておき, スーパーバイザーのプロセスを生成すると,自動的に子プロセスを生成してくれるものだ.
以下のコードでも,スーパーバイザー Foo.Supervisor.start_link/0
で生成すると,自動的に Foo.start_link/2
が呼ばれているのがわかるだろう.
defmodule Foo do
use GenServer
def start_link(state, opts \\ []) do
IO.puts "Foo.start_link/2, state: #{inspect state}, opts: #{inspect opts}"
GenServer.start_link(__MODULE__, state, opts)
end
end
defmodule Foo.Supervisor do
use Supervisor
def start_link do
IO.puts "Foo.Supervisor.start_link/0"
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init(args) do
IO.puts "Foo.Supervisor.init/1, args: #{inspect args}"
children = [
worker(Foo, [[:hello]])
]
IO.puts "start supervise, children: #{inspect children}"
supervise(children, strategy: :one_for_one)
end
end
Foo.Supervisor.start_link
#> Foo.Supervisor.start_link/0
#> Foo.Supervisor.init/1, args: []
#> start supervise, children: [{Foo, {Foo, :start_link, [[:hello]]}, :permanent, 5000, :worker, [Foo]}]
#> Foo.start_link/2, state: [:hello], opts: []
Supervisorのプロセス生成時とは別に監視対象のプロセスを生成する
4 の simple_one_for_one
だけは,監視対象のプロセスを生成するタイミングが異なり,
スーパーバイザーのプロセスを生成しても,自動的に子プロセスを生成してくれはしない.
以下のコードで Foo.start_link/2
が呼ばれなくなっているのがわかるだろう.
defmodule Foo do
use GenServer
def start_link(state, opts \\ []) do
IO.puts "Foo.start_link/2, state: #{inspect state}, opts: #{inspect opts}"
GenServer.start_link(__MODULE__, state, opts)
end
end
defmodule Foo.Supervisor do
use Supervisor
def start_link do
IO.puts "Foo.Supervisor.start_link/0"
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init(args) do
IO.puts "Foo.Supervisor.init/1, args: #{inspect args}"
children = [
# worker(Foo, [[:hello]]) から変更
worker(Foo, [])
]
IO.puts "start supervise, children: #{inspect children}"
# supervise(children, strategy: :one_for_one) から変更
supervise(children, strategy: :simple_one_for_one)
end
end
Foo.Supervisor.start_link
#> Foo.Supervisor.start_link/0
#> Foo.Supervisor.init/1, args: []
#> start supervise, children: [{Foo, {Foo, :start_link, []}, :permanent, 5000, :worker, [Foo]}]
simple_one_for_oneで子プロセスをどのように生成するか?
Supervisor.start_child/2
を呼ぶと生成される.
defmodule Foo do
use GenServer
def start_link(state, opts \\ []) do
IO.puts "Foo.start_link/2, state: #{inspect state}, opts: #{inspect opts}"
GenServer.start_link(__MODULE__, state, opts)
end
end
defmodule Foo.Supervisor do
use Supervisor
def start_link do
IO.puts "Foo.Supervisor.start_link/0"
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def start_child(args) do
Supervisor.start_child(__MODULE__, args)
end
def init(args) do
IO.puts "Foo.Supervisor.init/1, args: #{inspect args}"
children = [
worker(Foo, [])
]
IO.puts "start supervise, children: #{inspect children}"
supervise(children, strategy: :simple_one_for_one)
end
end
Foo.Supervisor.start_link
#> Foo.Supervisor.start_link/0
#> Foo.Supervisor.init/1, args: []
#> start supervise, children: [{Foo, {Foo, :start_link, []}, :permanent, 5000, :worker, [Foo]}]
Foo.Supervisor.start_child([[:foo]])
#> Foo.start_link/2, state: [:foo], opts: []
Foo.Supervisor.start_child([[:bar]])
#> Foo.start_link/2, state: [:bar], opts: []
スーパーバイザーのプロセスを生成した後,任意のタイミングで監視対象の子プロセスを生成できると何が嬉しいのだろうか?
それは子プロセスの生成時に,任意のパラメータを受けとれるようになることだ. こうすると,動的に生成したプロセスを監視することができる.
例えば,リクエストを受けつけるような Web サーバーのプロセスを生成しておき,リクエストは simple_one_for_one で処理するようにしておく. すると,リクエストの処理中にエラーが起きてプロセスが死んだり,特定のリクエスト処理が長時間かかっても,Web サーバーのプロセスや,他のリクエストのプロセスには影響を及ぼさない.
こういった処理をすることは良くあるだろうし,その場合に便利だろう.
具体例が やってみた -URL外形監視- にあるので参考にするとよい.
まとめ
Supervisor の子プロセスに関する振舞いは,大きく 2 つにわけられる.
- Supervisorのプロセス生成時に監視対象のプロセスも生成する
- Supervisorのプロセス生成時とは別に監視対象のプロセスを生成する
このうち,後者を行うための戦略 simple_one_for_one
のメリット,「任意のパラメータを与えて子プロセスを生成できる」について書いた.