環境
$ lsb_release -d
Description: Ubuntu 18.04.2 LTS
$ elixir -v
Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Elixir 1.8.1 (compiled with Erlang/OTP 20)
17.3 バンドで練習
simple_one_for_one スーパバイザを使う
:simple_one_for_one
のとき、子の仕様(全て一律)は辞書で保持されるらしい。
それ以外の戦略の場合はリストで保持されるので子が多い場合は前者がいいんだね。
Supervisor.start_child(sup, [arg, ...])
とすると、
:simple_one_for_one
の sup
の子仕様 start: {mod, :start_link, args}
を探し、
apply(mod, :start_link, args++[arg, ...])
を呼び出す。
ややこいね。
Elixir では非推奨だけどやってみよう。
defmodule Band do
use Supervisor
alias __MODULE__, as: Me
alias __MODULE__.Musician
...
# 追加
@impl true
def init(:jamband) do
child = %{
# 親が子を識別するための child_id
id: :jam_musician,
start: {Musician, :start_link, []},
restart: :temporary,
}
Supervisor.init([child], strategy: :simple_one_for_one)
end
...
end
iex(1)> Band.start_link(:jamband)
{:ok, #PID<0.147.0>}
iex(2)> Supervisor.start_child(Band, [:djembe, :good])
Dorothy Frizzle(good djembe) は部屋に入った。
{:ok, #PID<0.149.0>}
Dorothy Frizzle(good djembe) の音はいいね!
...
iex(3)> Supervisor.start_child(Band, [:djembe, :good])
{:error, {:already_started, #PID<0.149.0>}}
...
Supervisor.start_chid(Band, [:djembe, :good])
が、
apply(Band.Musician, :start_link, []++[:djembe, :good])
を呼び出す。
Musician
の定義を思い出すと、この :djembe
で名前を登録するのであった。
なわけで、2回目の start_child/2
で名前衝突が起きている。
こういう状況に便利な Registry
というものがあるが、
話が長くなるので、ここでは E本の流れに準じ単に違うアトムを渡す。
...
iex(4)> Supervisor.start_child(Band, [:drum, :good])
Wanda Perlstein(good drum) は部屋に入った。
{:ok, #PID<0.152.0>}
...
iex(5)> Supervisor.start_child(Band, [:guitar, :good])
Phoebe Li(good guitar) は部屋に入った。
{:ok, #PID<0.154.0>}
...
iex(6)> Supervisor.terminate_child(Band, :djembe)
{:error, :simple_one_for_one} # あれれ?
...
iex(7)> Band.Musician.stop(:djembe)
Dorothy Frizzle(good djembe) は部屋を出た。
:ok
...
うまい話ばかりじゃないですよ
うーん、話が食い違うねー。
Erlang 20 でコンパイルされた Elixir なんだけど、どーして?
...
iex(6)> Supervisor.terminate_child(Band, Process.whereis(:djembe))
マネージャーは怒りバンドを解散させた!
Dorothy Frizzle(good djembe) は地下鉄へ去っていった。
:ok
誤植に耐えてよく頑張った!感動した!
※ DynamicSupervisor
を使ってみる
DynamicSupervisor — Elixir vx.x.x
Supervisor
から :simple_one_for_one
を排除したいのね。
別のモジュールにしちゃった方が見通しが良いでしょということかな。
$ touch lib/band/jam.ex
defmodule Band.Jam do
use DynamicSupervisor
alias __MODULE__, as: Me
alias Band.Musician
### use で child_spec/1 が自動的に定義される。 ###
# def child_spec(arg) do
# %{
# id: __MODULE__,
# start: {__MODULE__, :start_link, [arg]},
# type: supervisor
# }
# end
def start_link do
DynamicSupervisor.start_link(Me, [], name: Me)
end
def stop, do: DynamicSupervisor.stop(Me)
def start_child(role, skill) do
child = %{
id: Musician,
start: {Musician, :start_link, [role, skill]},
restart: :temporary
}
DynamicSupervisor.start_child(Me, child)
end
def terminate_child(role) do
pid = Process.whereis(role)
DynamicSupervisor.terminate_child(Me, pid)
end
@impl true
def init([]) do
# 子仕様リストとして返却しなくて済むようになった。
DynamicSupervisor.init(strategy: :one_for_one)
end
end
iex(1)> Band.Jam.start_link()
{:ok, #PID<0.166.0>}
iex(2)> Band.Jam.start_child(:djembe, :good)
Carlos Frizzle(good djembe) は部屋に入った。
{:ok, #PID<0.168.0>}
Carlos Frizzle(good djembe) の音はいいね!
...
iex(3)> Band.Jam.start_child(:drum, :good)
Dorothy Li(good drum) は部屋に入った。
{:ok, #PID<0.170.0>}
...
iex(4)> Band.Jam.start_child(:guitar, :good)
Wanda Terese(good guitar) は部屋に入った。
{:ok, #PID<0.172.0>}
...
iex(5)> Band.Jam.terminate_child(:djembe)
マネージャーは怒りバンドを解散させた!
Carlos Frizzle(good djembe) は地下鉄へ去っていった。
:ok
...
iex(6)> Band.Jam.stop()
マネージャーは怒りバンドを解散させた!
Dorothy Li(good drum) は地下鉄へ去っていった。
マネージャーは怒りバンドを解散させた!
Wanda Terese(good guitar) は地下鉄へ去っていった。
:ok
うむ、言語設計者の哲学を感じるね。
動的に子を追加していく場合、今回の様にタプルで名前をつけていくのはよろしくない。
タプルはガベージコレクションの対象ではないからだ。
自前で対策を踏むよりも Registry
を利用した方が良さそうだ。
だが、この章の段階では手を出すには早すぎるだろう。
次章からはアプリケーションだ。