sysモジュールには、ビヘイビアを使って実装された、シンプルなデバッグ用プロセスのための関数が定義されています。
これ以外にも、一緒に使用できる関数がproc_libモジュールに定義されており、これを使って 特別なプロセス や、標準のビヘイビアを利用しないがOTPの設計原則に即したプロセスなどを実装することができます。これらを使って、非標準の、ユーザ定義のビヘイビアを実装することもできます。
sysとproc_libの両方共、標準ライブラリのapplicationに属しています。
sysモジュールには、ビヘイビアを利用して実装したプロセスのデバッグを簡単に行うための関数がいくつか定義されています。 get_event の章で説明したcode_lockの例を使って紹介します。
% erl
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
Eshell V5.2.3.6 (abort with ^G)
1> code_lock:start_link([1,2,3,4]).
{ok,<0.32.0>}
2> sys:statistics(code_lock, true).
ok
3> sys:trace(code_lock, true).
ok
4> code_lock:button(4).
*DBG* code_lock got event {button,4} in state closed
ok
*DBG* code_lock switched to state closed
5> code_lock:button(3).
*DBG* code_lock got event {button,3} in state closed
ok
*DBG* code_lock switched to state closed
6> code_lock:button(2).
*DBG* code_lock got event {button,2} in state closed
ok
*DBG* code_lock switched to state closed
7> code_lock:button(1).
*DBG* code_lock got event {button,1} in state closed
ok
OPEN DOOR
*DBG* code_lock switched to state open
*DBG* code_lock got event timeout in state open
CLOSE DOOR
*DBG* code_lock switched to state closed
8> sys:statistics(code_lock, get).
{ok,[{start_time,{{2003,6,12},{14,11,40}}},
{current_time,{{2003,6,12},{14,12,14}}},
{reductions,333},
{messages_in,5},
{messages_out,0}]}
9> sys:statistics(code_lock, false).
ok
10> sys:trace(code_lock, false).
ok
11> sys:get_status(code_lock).
{status,<0.32.0>,
{module,gen_fsm},
[[{'$ancestors',[<0.30.0>]},
{'$initial_call',{gen,init_it,
[gen_fsm,<0.30.0>,<0.30.0>,
{local,code_lock},
code_lock,
[1,2,3,4],
[]]}}],
running,<0.30.0>,[],
[code_lock,closed,{[],[1,2,3,4]},code_lock,infinity]]}
6.2 Special Processes
このセクションでは、OTP設計原則に即したプロセスの実装方法を紹介します。
システムメッセージは特別な意味を持つメッセージで、監視ツリーの内部で使用されます。システムメッセージの例としては、トレース情報の出力のリクエストや、プロセス実行の停止や再開などがあります。標準のビヘイビアを利用して実装されたプロセスは、自動でこれらのメッセージを解釈します。
概要 の章で、シンプルなサーバの実装例を紹介しましたが、ここではsysとproc_libを用いて、監視ツリーで使用できるように実装していきます。
-module(ch4).
-export([start_link/0]).
-export([alloc/0, free/1]).
-export([init/1]).
-export([system_continue/3, system_terminate/4,
write_debug/3]).
start_link() ->
proc_lib:start_link(ch4, init, [self()]).
alloc() ->
ch4 ! {self(), alloc},
receive
{ch4, Res} ->
Res
end.
free(Ch) ->
ch4 ! {free, Ch},
ok.
init(Parent) ->
register(ch4, self()),
Chs = channels(),
Deb = sys:debug_options([]),
proc_lib:init_ack(Parent, {ok, self()}),
loop(Chs, Parent, Deb).
loop(Chs, Parent, Deb) ->
receive
{From, alloc} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, alloc, From}),
{Ch, Chs2} = alloc(Chs),
From ! {ch4, Ch},
Deb3 = sys:handle_debug(Deb2, {ch4, write_debug},
ch4, {out, {ch4, Ch}, From}),
loop(Chs2, Parent, Deb3);
{free, Ch} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, {free, Ch}}),
Chs2 = free(Ch, Chs),
loop(Chs2, Parent, Deb2);
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent,
ch4, Deb, Chs)
end.
system_continue(Parent, Deb, Chs) ->
loop(Chs, Parent, Deb).
system_terminate(Reason, Parent, Deb, Chs) ->
exit(Reason).
write_debug(Dev, Event, Name) ->
io:format(Dev, "~p event = ~p~n", [Name, Event]).
ch4内で使用されている、sysのシンプルなデバッグ関数は次のように使用します。
% erl
Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
Eshell V5.2.3.6 (abort with ^G)
1> ch4:start_link().
{ok,<0.30.0>}
2> sys:statistics(ch4, true).
ok
3> sys:trace(ch4, true).
ok
4> ch4:alloc().
ch4 event = {in,alloc,<0.25.0>}
ch4 event = {out,{ch4,ch1},<0.25.0>}
ch1
5> ch4:free(ch1).
ch4 event = {in,{free,ch1}}
ok
6> sys:statistics(ch4, get).
{ok,[{start_time,{{2003,6,13},{9,47,5}}},
{current_time,{{2003,6,13},{9,47,56}}},
{reductions,109},
{messages_in,2},
{messages_out,1}]}
7> sys:statistics(ch4, false).
ok
8> sys:trace(ch4, false).
ok
9> sys:get_status(ch4).
{status,<0.30.0>,
{module,ch4},
[[{'$ancestors',[<0.25.0>]},{'$initial_call',{ch4,init,[<0.25.0>]}}],
running,<0.25.0>,[],
[ch1,ch2,ch3]]}
proc_libモジュール内の関数は、プロセスを起動されるのに使用するべきです。利用可能な関数がいkつかあります。例えば、spawn_link/3,4は非同期の起動に、start_link/3,4,5は同期起動に使うことができます。
上記の関数を使って起動したプロセスは、ancestorや、initial callなどの監視ツリー内のプロセスが必要とする情報を格納しています。
また、プロセスが通常の理由以外で終了したり、シャットダウンした場合には、クラッシュレポート(SASLユーザガイド参照)が生成されます。
例えば、同期起動が使用されたとします。 ch4:start_link() を呼んでプロセスをスタートさせます。
start_link() ->
proc_lib:start_link(ch4, init, [self()]).
ch4:start_link は proc_lib:start_link 関数を呼び出します。この関数はモジュール名、関数名、引き数のリストをパラメータに取り、新しいプロセスを生成してリンクします。
init の中では、名前の登録を含む、すべての初期化を完了させます。新しいプロセスは、親のプロセスに対して、起動したことを知らせなければなりません。
init(Parent) ->
...
proc_lib:init_ack(Parent, {ok, self()}),
loop(...).
proc_lib:start_link は同期実行されるため、 proc_lib:init_ack を呼び出すまではリターンしません。
sysモジュールデバッグ環境をサポートさせるには、 sys:debug_options/1 を使用して、 Deb という項を初期化する必要があります。
init(Parent) ->
...
Deb = sys:debug_options([]),
...
loop(Chs, Parent, Deb).
sys:debug_options/1 はリスト型のオプションを引数に取ります。ここでは空のリストを渡していますが、これは初期化の際には、デバッグ機能は利用しない、という意味です。使用できるオプションについては、sys(3)を参照してください。
ログを取ったり、トレースしたいシステムイベントごとに、次の関数を呼び出す必要があります。
sys:handle_debug(Deb, Func, Info, Event) => Deb1
Func は {Module, Name} (もしくはfun)のタプルで、手レース出力のフォーマットに使用される、ユーザ定義関数を指定します。システムイベントごとに、 Module:Name(Dev, Event, Info) という形式でフォーマット関数が呼ばれます。
- Devは出力が書き出されるIOデバイスです。詳しくはio(3)を参照してください。
- Event 、 Info はそのまま handle_debug に渡されます。
handle_debug は、更新されたデバッグ構造体の Deb1 を返します。
次のサンプルでは、メッセージの入力と、出力のそれぞれに対して、 handle_debug を呼び出しています。フォーマット関数の Func としては、 io:format/3 を利用して情報をプリントする ch4:write_debug/3 が渡されています。
loop(Chs, Parent, Deb) ->
receive
{From, alloc} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, alloc, From}),
{Ch, Chs2} = alloc(Chs),
From ! {ch4, Ch},
Deb3 = sys:handle_debug(Deb2, {ch4, write_debug},
ch4, {out, {ch4, Ch}, From}),
loop(Chs2, Parent, Deb3);
{free, Ch} ->
Deb2 = sys:handle_debug(Deb, {ch4, write_debug},
ch4, {in, {free, Ch}}),
Chs2 = free(Ch, Chs),
loop(Chs2, Parent, Deb2);
...
end.
write_debug(Dev, Event, Name) ->
io:format(Dev, "~p event = ~p~n", [Name, Event]).
システムメッセージは次のような形式で受信されます。
{system, From, Request}
これらのメッセージの中身と意味はプロセスが解釈する必要はありません。その代わりに次の関数を呼び出します。
sys:handle_system_msg(Request, From, Parent, Module, Deb, State)
この関数はリターンしません。この関数はシステムメッセージを捕まえて、もしプロセスの実行を継続すべき場合には、次のように呼び出します。
Module:system_continue(Parent, Deb, State)
また、もしプロセスを停止させるべき場合は次の関数を呼び出します。
Module:system_terminate(Reason, Parent, Deb, State)
監視ツリー上のプロセスは、その親のプロセスと同じ理由で終了されることが期待されています。
例:
loop(Chs, Parent, Deb) ->
receive
...
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent,
ch4, Deb, Chs)
end.
system_continue(Parent, Deb, Chs) ->
loop(Chs, Parent, Deb).
system_terminate(Reason, Parent, Deb, Chs) ->
exit(Reason).
もし、終了をトラップする特別なプロセスが設定されていて、親プロセスが終了すると、同じ理由で終了するのが期待される動作です。
init(...) ->
...,
process_flag(trap_exit, true),
...,
loop(...).
loop(...) ->
receive
...
{'EXIT', Parent, Reason} ->
..maybe some cleaning up here..
exit(Reason);
...
end.
ユーザ定義のビヘイビアを実装する場合は、特別なプロセスと同じようなコードを書いて、特別なタスクを処理するために、コールバックモジュール内の関数を呼ぶようにすればできます。
もし、OTPのビヘイビアと同じように、コールバック関数の定義がされていないという警告を出したいのであれば、次の関数を定義して、エクスポートします。
behaviour_info(callbacks) ->
[{Name1,Arity1},...,{NameN,ArityN}].
{Name,Arity} というタプルによって、コールバック関数の名前とアリティを定義します。
コンパイラが Mod モジュールの中で -behaviour(Behaviour). というモジュール属性を検知すると、 Behaviour:behaviour_info(callbacks) を呼び出し、その結果と Mod モジュールが実際にエクスポートしている関数を比較します。もし、見つからないコールバック関数があれば、警告を発します。
サンプル:
%% ユーザ定義ビヘイビアモジュール
-module(simple_server).
-export([start_link/2,...]).
-export([behaviour_info/1]).
behaviour_info(callbacks) ->
[{init,1},
{handle_req,1},
{terminate,0}].
start_link(Name, Module) ->
proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
init(Parent, Name, Module) ->
register(Name, self()),
...,
Dbg = sys:debug_options([]),
proc_lib:init_ack(Parent, {ok, self()}),
loop(Parent, Module, Deb, ...).
...
コールバックモジュール:
-module(db).
-behaviour(simple_server).
-export([init/0, handle_req/1, terminate/0]).
...
Copyright (c) 1991-2009 Ericsson AB