OTPというか、gen_server の概要を掴むためのメモ
汎用サーバーモジュールを作る
- サーバー共通部分
-module(my_server).
-export([start/2, start_link/2, call/2, cast/2, reply/2]).
%%% 公開API
start(Module, InitialState) ->
spawn(fun() -> init(Module, InitialState) end).
start_link(Module, InitialState) ->
spawn_link(fun() -> init(Module, InitialState) end).
call(Pid, Msg) ->
Ref = erlang:monitor(process, Pid),
Pid ! {sync, self(), Ref, Msg},
receive
{Ref, Reply} ->
erlang:demonitor(Ref, [flush]),
Reply;
{'DOWN', Ref, process, Pid, Reason} ->
erlang:error(Reason)
after 5000 ->
erlang:error(timeout)
end.
cast(Pid, Msg) ->
Pid ! {async, Msg},
ok.
reply({Pid, Ref}, Reply) ->
Pid ! {Ref, Reply}.
%%% プライベート関数
init(Module, InitialState) ->
loop(Module, Module:init(InitialState)).
loop(Module, State) ->
receive
{async, Msg} ->
loop(Module, Module:handle_cast(Msg, State));
{sync, Pid, Ref, Msg} ->
loop(Module, Module:handle_call(Msg, {Pid, Ref}, State))
end.
- アプリケーションコード
-module(kitty_server2).
-export([start_link/0, order_cat/4, return_cat/2, close_shop/1]).
-export([init/1, handle_call/3, handle_cast/2]).
-record(cat, {name, color=green, description}).
%%% クライアントAPI
start_link() -> my_server:start_link(?MODULE, []).
%% 同期呼び出し
order_cat(Pid, Name, Color, Description) ->
my_server:call(Pid, {order, Name, Color, Description}).
%% 非同期呼び出し
return_cat(Pid, Cat = #cat{}) ->
my_server:cast(Pid, {return, Cat}).
%% 同期呼び出し
close_shop(Pid) ->
my_server:call(Pid, terminate).
%%% サーバー関数
init([]) -> []. %% ここでは何もしない
handle_call({order, Name, Color, Description}, From, Cats) ->
if Cats =:= [] ->
my_server:reply(From, make_cat(Name, Color, Description)),
Cats;
Cats =/= [] ->
my_server:reply(From, hd(Cats)),
tl(Cats)
end;
handle_call(terminate, From, Cats) ->
my_server:reply(From, ok),
terminate(Cats).
handle_cast({return, Cat = #cat{}}, Cats) ->
[Cat|Cats].
%%% プライベート関数
make_cat(Name, Col, Desc) ->
#cat{name=Name, color=Col, description=Desc}.
terminate(Cats) ->
[io:format("~p was set free.~n",[C#cat.name]) || C <- Cats],
exit(normal).
my_server, kitty_server2 の概要
- my_server.erl には軽量プロセス化されるメインループ(loop関数)がある
- my_server:call, my_server:cast はこのメインループのPidに対してsendする
- メインループは渡された引数を元にサーバー関数である handle_cast, handle_call を呼び出す
- kitty_server2 はサーバーの機能を呼び出すための公開APIと、実際に処理されるhandle_cast, handle_call が定義されている
- kitty_server2自体はメインループを持たない
- メインループやサーバーの状態保存は my_serverモジュールが持つ
- kitty_server2 はサーバーに実際に提供したい機能のみを定義すれば良い
- サーバーとして機能は my_serverモジュールが持つ
- kitty_server2自体はメインループを持たない
- この my_server モジュールがOTPの gen_server の概要的モデルとなる
動作イメージ
- my_server, kitty_server2 は大体以下のような関係、役割分担となる。
gen_server ビヘイビアのコールバック関数
init/1
サーバー初期化関数
- 返り値
-
{ok, State}
-
State
がサーバーが保持する状態として、プロセスのメインループへ渡される。
-
-
{ok, State, TimeOut}
-
TimeOut
でメッセージ受信の締切を設定可能 - タイムアウト時に
timeout
メッセージが送信される- タイムアウトメッセージは
handle_info/2
で処理する
- タイムアウトメッセージは
- 普通は
erlang:start_time/3
などで手動制御するほうが良い
-
-
{ok, State, hibernate}
- メッセージ受信待ちの間、処理能力を犠牲にして省メモリ化する
- メッセージ受信の間隔が長くなると予想される場合に使うこともある
-
{stop, Reason}
- 処理失敗時に返す
ignore
-
handle_call/3
同期メッセージの処理関数
- 引数
Request
From
State
- 返り値
{reply, Reply, NewState}
{reply, Reply, NewState, TimeOut}
{reply, Reply, NewState, hibernate}
{noreply, NewState}
{noreply, NewState, TimeOut}
{noreply, NewState, hibernate}
{stop, Reason, Reply, NewState}
{stop, Reason, NewState}
-
TimeOut
,hibernate
の役割はinit/1
と同じ。 -
noreply
のときは自分で返信処理を実装する- 返信を遅延させたいときなど
- 返信処理を忘れるとタイムアウトしてクラッシュする
- 返信処理には
gen_server:reply/2
を使う
handle_cast/2
非同期メッセージの処理関数
- 引数
Message
State
- 返り値
{noreply, NewState}
{noreply, NewState, TimeOut}
{noreply, NewState, hibernate}
{stop, Reason, NewState}
handle_info/2
handle_call/3
や handle_cast/2
で予期していないメッセージを処理する関数
!
演算子で直接送られてきたメッセージや、 init/1
の timeout
、モニターからの通知、 EXIT
シグナルなどを処理する
- 引数
Message
State
- 返り値
{noreply, NewState}
{noreply, NewState, TimeOut}
{noreply, NewState, hibernate}
{stop, Reason, NewState}
termminate/2
プロセスの終了処理を実装する
- 以下のタイミングで呼ばれる
-
handle_*
な関数が{stop, Reason, NewState}
or{stop, Reason, Reply, NewState}
が返したとき -
gen_server
が終了を補足している場合に、親プロセスが死んだとき
-
- 引数
Reason
State
code_change/3
コードアップグレード処理
- 引数
PreviousVersion
State
Extra
gen_server の関数
gen_server:start_link/3-4, start/3-4
コールバックモジュールの init/1
を呼び出す
- 引数
- コールバックモジュール
- 例えば同じモジュール内にコールバック関数が定義されていたら
?MODULE
と指定すればよい
- 例えば同じモジュール内にコールバック関数が定義されていたら
-
init/1
にわたすEralng項 - 実行中のサーバーのデバッグオプション
-
{local, 名前}
せサーバーの登録名を指定できる
- コールバックモジュール
呼び出し例
gen_server:start_link(?MODULE, [], []).
gen_server:call/2-3
コールバックモジュールの handle_call/3
を呼び出す
- 引数
- サーバープロセスのPid
-
handle_call
にわたすErlang項 - タイムアウト
- 指定なしの場合はデフォルトで5秒に設定される
- タイムアウト時はクラッシュする
-
infinity
指定でタイムアウト無し
呼び出し例
gen_server:call(Pid, {order, Name, Color}).
gen_server:cast/2
コールバックモジュールの handle_cast/2
を呼び出す
- 引数
- サーバープロセスのPid
-
handle_cast
にわたすErlang項
呼び出し例
gen_server:cast(Pid, {return, State}).