環境
$ 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)
19.1 私のもう一台の車はプールです
前章の p_pool
ディレクトリを任意の別名に変更し、
新たにプロジェクトを --sup
付きで作成する。
「このアプリケーションをスーパーバイザで管理する」と指示している。
続いて、プロジェクトの lib
以下に古い方のファイルをコピーする。
$ ls -F
p_pool/
$ mv p_pool p_pool_old
$ mix new p_pool --module PPool --sup
...
$ cp -r p_pool_old/lib/* p_pool/lib/
$ cd p_pool
$ tree lib
lib
├── nagger.ex
├── p_pool
│ ├── application.ex
│ ├── poolvisor.ex
│ ├── stash.ex
│ ├── supervisor.ex
│ └── workervisor.ex
└── p_pool.ex
1 directory, 7 files
application.ex
が追加されていることが確認できる。
19.2 アプリケーションリソースファイル
プロジェクトをコンパイルしてみる。
$ mix compile
Compiling 7 files (.ex)
Generated p_pool app
$ tree _build
_build
└── dev
└── lib
└── p_pool
├── consolidated
│ ├── Elixir.Collectable.beam
│ ├── Elixir.Enumerable.beam
│ ├── Elixir.IEx.Info.beam
│ ├── Elixir.Inspect.beam
│ ├── Elixir.List.Chars.beam
│ └── Elixir.String.Chars.beam
└── ebin
├── Elixir.Nagger.beam
├── Elixir.PPool.Application.beam
├── Elixir.PPool.Poolvisor.beam
├── Elixir.PPool.Stash.beam
├── Elixir.PPool.Supervisor.beam
├── Elixir.PPool.Workervisor.beam
├── Elixir.PPool.beam
└── p_pool.app
5 directories, 14 files
$ cat _build/dev/lib/p_pool/ebin/p_pool.app
{application,p_pool,
[{applications,[kernel,stdlib,elixir,logger]},
{description,"p_pool"},
{modules,['Elixir.Nagger','Elixir.PPool',
'Elixir.PPool.Application','Elixir.PPool.Poolvisor',
'Elixir.PPool.Stash','Elixir.PPool.Supervisor',
'Elixir.PPool.Workervisor']},
{registered,[]},
{vsn,"0.1.0"},
{mod,{'Elixir.PPool.Application',[]}}]}.
おっと Nagger
までアプリケーションに入れてしまった。
はてさて、どうしたものか。
とりあえず lib
以下の掃除をし、
test
ディレクトリに nagger.ex
を退避しておこう。
$ mix clean
$ tree _build
_build
└── dev
└── lib
2 directories, 0 files
$ mv lib/nagger.ex test/
19.3 プールを変換する
E本のアプリケーション・リソース・ファイルに合わせるには、
Mix.Project
の application/0
に registered
を追加する。
defmodule PPool.MixProject do
use Mix.Project
def project do
[
app: :p_pool,
version: "0.1.0",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
extra_applications: [:logger],
mod: {PPool.Application, []},
registered: [PPool.Supervisor] # 追加
]
end
defp deps do
[]
end
end
$ mix compile
Compiling 6 files (.ex)
Generated p_pool app
$ cat _build/dev/lib/p_pool/ebin/p_pool.app
{application,p_pool,
[{applications,[kernel,stdlib,elixir,logger]},
{description,"p_pool"},
{modules,['Elixir.PPool','Elixir.PPool.Application',
'Elixir.PPool.Poolvisor','Elixir.PPool.Stash',
'Elixir.PPool.Supervisor','Elixir.PPool.Workervisor']},
{vsn,"0.1.0"},
{mod,{'Elixir.PPool.Application',[]}},
{registered,['Elixir.PPool.Supervisor']}]}.
19.4 アプリケーションビヘイビア
Mix が作った雛形はこうなっている。
defmodule PPool.Application do
@moduledoc false
use Application
# PPool.MixProject の application/0 の mod で呼ばれる
def start(_type, _args) do
# 簡易なスーパーバイザが書かれている
children = []
opts = [strategy: :one_for_one, name: PPool.Supervisor]
Supervisor.start_link(children, opts)
end
end
19.5 カオスからアプリケーションへ
雛形を変更する。
defmodule PPool.Application do
@moduledoc false
use Application
def start(:normal, _args) do
{:ok, _pid} = PPool.Supervisor.start_link()
end
end
上記に合わせて PPool
(p_pool.ex
) から start_link/1
stop/1
を削除しておく。
$ mix format
$ iex -S mix
iex(1)> c "test/nagger.ex"
[Nagger]
iex(2)> PPool.start_pool(Nagger, :start_link, [])
{:ok, #PID<0.189.0>}
iex(3)> PPool.run(Nagger, [self(), make_ref(), 500, 10])
{:ok, #PID<0.193.0>}
iex(4)> PPool.run(Nagger, [self(), make_ref(), 500, 10])
{:ok, #PID<0.195.0>}
iex(5)> PPool.run(Nagger, [self(), make_ref(), 500, 10])
:no_alloc
iex(6)> Received down message.
Received down message.
flush()
{#PID<0.193.0>, #Reference<0.368367721.402391043.109350>}
{#PID<0.193.0>, #Reference<0.368367721.402391043.109350>}
...
:ok
あれれ、標準出力がズレてら。
アプリケーションに包む前はこうだった。
...
:no_alloc
Received down message.
Received down message.
iex(6)> flush()
...
アプリケーションに包んで、非同期なコールバック中で標準出力するとズレるみたい。
どうしてでしょう?まぁ、副作用なんで深く考えるのはやめておこう。
ちなみに、E本のソースコードは著者の GitHub に公開されている。
話が長くなるのでテストについては今回はパスで。
iex(7)> :application.which_applications()
[
{:p_pool, 'p_pool', '0.1.0'},
{:logger, 'logger', '1.8.1'},
{:mix, 'mix', '1.8.1'},
{:iex, 'iex', '1.8.1'},
{:elixir, 'elixir', '1.8.1'},
{:compiler, 'ERTS CXC 138 10', '7.3.2'},
{:stdlib, 'ERTS CXC 138 10', '3.8.1'},
{:kernel, 'ERTS CXC 138 10', '6.3.1'}
]
iex(8)> Application.stop(:p_pool)
:ok
06:55:28.837 [info] Application p_pool exited: :stopped
「私たちのアプリケーションが temporary になっているのはなぜでしょう」と、
:application.start/2
(Application.start/2
) について説明が続くが、
Elixir の場合も規定値は :temporary
だ。
PPool.MixProject
の project
に
雛形で start_permanent: Mix.env() == :prod
と書いてある。
「もし本番環境ならば permanent で起動してね」という意味だ。
$ MIX_ENV=prod iex -S mix
とやれば本番環境になる。
このプロジェクトの現段階では dev
と prod
に違いは無い。
19.6 ライブラリアプリケーション
プロジェクトを作るとき --sup
が無ければライブラリアプリケーション。
20.2 アプリケーションよ、走れ
もう20章なんか実装する気にならんわ。
例えば、19章の PPool
プロジェクトのパスが ~/src/p_pool
だとすると、
$ ERL_LIBS='~/src/p_pool/_build/dev/lib/p_pool' iex
iex(1)> Application.load(:p_pool)
:ok
iex(2)> Application.start(:p_pool)
:ok
iex(3)> PPool.start_pool(Foo, :start, [])
{:ok, #PID<0.113.0>}
やっと普通の言語っぽく動き出した、なげーよ。
やっぱり Erlang/Elixir はサーバを書くための DSL なんだね。
20.4 複雑な終了
下記の定義で iex -S mix
する。
defmodule PPool.Application do
@moduledoc false
use Application
def start(:normal, _args) do
{:ok, _pid} = PPool.Supervisor.start_link()
end
def prep_stop(state) do
IO.puts("prep!")
state
end
def stop(_state) do
IO.puts("stop!")
:ok
end
end
iex(1)> Application.stop(:p_pool)
prep!
stop!
17:26:06.675 [info] Application p_pool exited: :stopped
:ok
次章はリリース。
うーん、まだやるかぁ、しんどいな・・・。