LoginSignup
2
1

More than 3 years have passed since last update.

すごいE本 第19-20章 on Elixir (OTPアプリケーション)

Last updated at Posted at 2019-05-09

環境

sh
$ 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 以下に古い方のファイルをコピーする。

sh
$ 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 アプリケーションリソースファイル

プロジェクトをコンパイルしてみる。

sh
$ 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 を退避しておこう。

sh
$ mix clean
$ tree _build
_build
└── dev
    └── lib

2 directories, 0 files

$ mv lib/nagger.ex test/

19.3 プールを変換する

E本のアプリケーション・リソース・ファイルに合わせるには、
Mix.Projectapplication/0registered を追加する。

p_pool/mix.exs
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
sh
$ 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 が作った雛形はこうなっている。

p_pool/lib/p_pool/application.ex
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 カオスからアプリケーションへ

雛形を変更する。

p_pool/lib/p_pool/application.ex
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 を削除しておく。

sh
$ mix format
$ iex -S mix
iex
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

あれれ、標準出力がズレてら。
アプリケーションに包む前はこうだった。

iex
...
:no_alloc
Received down message.
Received down message.
iex(6)> flush()
...

アプリケーションに包んで、非同期なコールバック中で標準出力するとズレるみたい。
どうしてでしょう?まぁ、副作用なんで深く考えるのはやめておこう。

ちなみに、E本のソースコードは著者の GitHub に公開されている。

GitHub - FranklinChen/learn-you-some-erlang: Code from the book "Learn You Some Erlang For Great Good!"

話が長くなるのでテストについては今回はパスで。

iex
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.MixProjectproject
雛形で start_permanent: Mix.env() == :prod と書いてある。
「もし本番環境ならば permanent で起動してね」という意味だ。
$ MIX_ENV=prod iex -S mix とやれば本番環境になる。
このプロジェクトの現段階では devprod に違いは無い。

19.6 ライブラリアプリケーション

プロジェクトを作るとき --sup が無ければライブラリアプリケーション。

20.2 アプリケーションよ、走れ

もう20章なんか実装する気にならんわ。

例えば、19章の PPool プロジェクトのパスが ~/src/p_pool だとすると、

sh
$ ERL_LIBS='~/src/p_pool/_build/dev/lib/p_pool' iex
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 する。

p_pool/lib/p_pool/application.ex
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
iex(1)> Application.stop(:p_pool)
prep!
stop!

17:26:06.675 [info]  Application p_pool exited: :stopped
:ok

次章はリリース。
うーん、まだやるかぁ、しんどいな・・・。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1