14章のフィボナッチサーバーでは…
- メッセージハンドリング
- スケジューラの状態管理
- プロセスに送る数字のキュー
- 生成された結果
- アクティブなプロセスのPIDのリスト
OTPは上記を行うライブラリを提供している
OTPサーバー 標準の名前を持つコールバック関数を1つ以上持つモジュールを書く
アキュームレーター: 再帰して自身を呼び出している間、パラメーターとして渡し続けられる値
サーバー ループのために再帰を使う 処理関数は状態を受け取り、処理し、状態を次の処理に渡す => OTPがやってくれる? 処理関数だけ書けば良い?
- サーバー開始時に数値を渡す
数値: 現在の状態 :nexe_numberというリクエストを受けると、現在の状態を返し、現在の状態を+1する
goh@goh% mix new sequence
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/sequence.ex
* creating test
* creating test/test_helper.exs
* creating test/sequence_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd sequence
mix test
Run "mix help" for more commands.use GenServer:
OTPの GenServer ビヘイビアをモジュールに追加する
ビヘイビアにはデフォルトのコールバックが定義されている
handle_call/3
- パラーメーター
- クライアントが渡した最初に引数の情報
- クライアントのPID
- サーバーの状態
- 戻り値: Tuple
- :reply
OTPに2つ目の要素を返すように指示している - 2つ目の要素
クライアントに返す値 - 3つ目の要素
次の状態
次回handle_callが呼び出されたときの3つ目のパラメーターになる
- :reply
goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]
Compiling 2 files (.ex)
warning: function init/1 required by behaviour GenServer is not implemented (in module Sequence.Server).
We will inject a default implementation for now:
def init(args) do
{:ok, args}
end
You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.
lib/sequence/server.ex:1
Generated sequence app
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.162.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.call(pid, :next_number)
102GenServer.start_link(module, args, options \\ []
新しいプロセスを開始し、リンクするようGenServerに依頼する
Kernel.spawn_link/3 のような関数
GenServer.call(server, request, timeout \\ 5000)
第2引数は handle_call の第1引数になる
2つ以上のパラメーターを渡す場合、Tupleを使う
goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.130.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.call(pid, :next_number)
102
> GenServer.call(pid, {:set_number, 999})
999
> GenServer.call(pid, :next_number)
999
> GenServer.call(pid, :next_number)
1000ハンドラーはTupleやリストを使って複数の値を返せる
goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]
Compiling 1 file (.ex)
warning: function init/1 required by behaviour GenServer is not implemented (in module Sequence.Server).
We will inject a default implementation for now:
def init(args) do
{:ok, args}
end
You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.
lib/sequence/server.ex:1
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.139.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.call(pid, {:set_number, 999})
999
> GenServer.call(pid, :next_number)
999
> GenServer.call(pid, :next_number)
1000
> GenServer.call(pid, {:factors, 0})
{:factors_of, 0, 1}
> GenServer.call(pid, {:factors, 10})
{:factors_of, 10, 11}handle_cast/2
- パラーメーター
- クライアントが渡した最初に引数の情報
- サーバーの状態
- 戻り値: Tuple
- :noreply
- 2つ目の要素
次の状態
iex.r(module
iex実行中に、指定されたモジュールのソースコードを再コンパイルする
再コンパイルしても稼働中のプロセスは更新されない
=>
新たにサーバーを起動する必要がある
goh@goh% iex -S mix
Erlang/OTP 21 [erts-10.0.8] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]
Compiling 1 file (.ex)
warning: function init/1 required by behaviour GenServer is not implemented (in module Sequence.Server).
We will inject a default implementation for now:
def init(args) do
{:ok, args}
end
You can copy the implementation above or define your own that converts the arguments given to GenServer.start_link/3 to the server state.
lib/sequence/server.ex:1
Interactive Elixir (1.7.3) - press Ctrl+C to exit (type h() ENTER for help)
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.139.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> GenServer.cast(pid, {:increment_number, 200})
:ok
> GenServer.call(pid, :next_number)
302サーバー実行時のオプション
[debug: [:trace]
以下の情報を表示する
- 呼び出し元
- 返り値
- 次の状態
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100, [debug: [:trace]])
{:ok, #PID<0.130.0>}
> GenServer.call(pid, :next_number)
*DBG* <0.130.0> got call next_number from <0.128.0>
*DBG* <0.130.0> sent 100 to <0.128.0>, new state 101
100
> GenServer.call(pid, :next_number)
*DBG* <0.130.0> got call next_number from <0.128.0>
*DBG* <0.130.0> sent 101 to <0.128.0>, new state 102
101[debug: [:statistics]]
以下の情報を表示する
- start_time: 開始日時
- current_time: 現在日時
- reductions:
仕事量
すべてのプロセスが公平にCPUを利用するために使われる値 - messeges_in: サーバーが受け取ったメッセージ数
- messeges_out: サーバーが送ったメッセージ数
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100, [debug: [:statistics]])
{:ok, #PID<0.144.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> :sys.statistics pid, :get
{:ok,
[
start_time: {{2018, 9, 23}, {15, 8, 52}},
current_time: {{2018, 9, 23}, {15, 9, 5}},
reductions: 100,
messages_in: 2,
messages_out: 2
]}:sys
プロセス同士がバックグラウンドでやり取りしているシステムメッセージのインターフェース
[debug: [:trace, :statistics]] が指定された場合、 :sys.trace :sys.statistics が呼び出される
http://erlang.org/doc/man/sys.html
サーバーを開始した後で、ON/OFFできる
> GenServer.call(pid, :next_number)
304
> :sys.trace pid, true
:ok
> GenServer.call(pid, :next_number)
*DBG* <0.144.0> got call next_number from <0.128.0>
*DBG* <0.144.0> sent 305 to <0.128.0>, new state 306
305
> :sys.trace pid, false
:ok
> GenServer.call(pid, :next_number)
306:sys.get_status(pid)
プロセスの状態をプロセスのタイプに応じて返す
gen_server の場合、コールバックモジュールの状態を返す
> :sys.get_status pid
{:status, #PID<0.144.0>, {:module, :gen_server},
[
[
"$ancestors": [#PID<0.128.0>, #PID<0.69.0>],
"$initial_call": {Sequence.Server, :init, 1}
],
:running,
#PID<0.128.0>,
[statistics: {{{2018, 9, 23}, {15, 8, 52}}, {:reductions, 21}, 2, 2}],
[
header: 'Status for generic server <0.144.0>',
data: [
{'Status', :running},
{'Parent', #PID<0.128.0>},
{'Logged events', []}
],
data: [{'State', 102}]
]
]}format_status 関数を定義することで、メッセージを変更することができる
> {:ok, pid} = GenServer.start_link(Sequence.Server, 100)
{:ok, #PID<0.139.0>}
> GenServer.call(pid, :next_number)
100
> GenServer.call(pid, :next_number)
101
> :sys.get_status pid
{:status, #PID<0.139.0>, {:module, :gen_server},
[
[
"$ancestors": [#PID<0.137.0>, #PID<0.73.0>],
"$initial_call": {Sequence.Server, :init, 1}
],
:running,
#PID<0.137.0>,
[],
[
header: 'Status for generic server <0.139.0>',
data: [
{'Status', :running},
{'Parent', #PID<0.137.0>},
{'Logged events', []}
],
data: [{'State', "My current state is '102', and I'm happy"}]
]
]}:observer.start()
サーバーモニタリングツールを起動する
ref. Pinterest の Elixometer: https://github.com/pinterest/elixometer
- A light Elixir wrapper around exometer.
- Elixometer allows you to define metrics and subscribe them automatically to the default reporter for your environment.