Table Of Contents

Previous topic

5. スーパバイザ・ビヘイビア

Next topic

7. アプリケーション

This Page

6. sysとproc_lib

sysモジュールには、ビヘイビアを使って実装された、シンプルなデバッグ用プロセスのための関数が定義されています。

これ以外にも、一緒に使用できる関数がproc_libモジュールに定義されており、これを使って 特別なプロセス や、標準のビヘイビアを利用しないがOTPの設計原則に即したプロセスなどを実装することができます。これらを使って、非標準の、ユーザ定義のビヘイビアを実装することもできます。

sysとproc_libの両方共、標準ライブラリのapplicationに属しています。

6.1. シンプルなデバッグ

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

6.2. 特別なプロセス

このセクションでは、OTP設計原則に即したプロセスの実装方法を紹介します。

  • このようなプロセスは、監視ツリー内で利用できるような方法でプロセスが起動できます。

システムメッセージは特別な意味を持つメッセージで、監視ツリーの内部で使用されます。システムメッセージの例としては、トレース情報の出力のリクエストや、プロセス実行の停止や再開などがあります。標準のビヘイビアを利用して実装されたプロセスは、自動でこれらのメッセージを解釈します。

6.2.1. サンプル

概要 の章で、シンプルなサーバの実装例を紹介しましたが、ここでは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]]}

6.2.2. プロセスのスタート

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_linkproc_lib:start_link 関数を呼び出します。この関数はモジュール名、関数名、引き数のリストをパラメータに取り、新しいプロセスを生成してリンクします。

init の中では、名前の登録を含む、すべての初期化を完了させます。新しいプロセスは、親のプロセスに対して、起動したことを知らせなければなりません。

init(Parent) ->
    ...
    proc_lib:init_ack(Parent, {ok, self()}),
    loop(...).

proc_lib:start_link は同期実行されるため、 proc_lib:init_ack を呼び出すまではリターンしません。

6.2.3. デバッグ機能

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
  • Debはデバッグ構造体です。
  • Func{Module, Name} (もしくはfun)のタプルで、手レース出力のフォーマットに使用される、ユーザ定義関数を指定します。システムイベントごとに、 Module:Name(Dev, Event, Info) という形式でフォーマット関数が呼ばれます。

    • Devは出力が書き出されるIOデバイスです。詳しくはio(3)を参照してください。
    • EventInfo はそのまま handle_debug に渡されます。
  • InfoFunc に追加の情報を渡すのに使用されます。これにはあらゆる項を設定することができ、そのまま渡されます。
  • Event はシステムイベントです。どんなシステムイベントで、どのように表現すべきかはユーザしだいですが、良く使用されるのは、最低限、メッセージ入力と出力はシステムイベントとして考えられいて、それぞれ、 {in,Msg[,From]} 、もしくは {out,Msg,To} という形式で表現されます。

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]).

6.2.4. システムメッセージの操作

システムメッセージは次のような形式で受信されます。

{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)

監視ツリー上のプロセスは、その親のプロセスと同じ理由で終了されることが期待されています。

  • システムメッセージから送られてきた RequestFrom は、そのまま handle_system_msg の呼び出し時に渡さなければなりません。
  • Parent は親のプロセスidです。
  • Module はモジュール名です。
  • Deb はデバッグ構造体です。
  • State は内部ステートを表す項で、 system_continue / system_terminate に渡されます。

例:

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.

6.3. ユーザ定義のビヘイビア

ユーザ定義のビヘイビアを実装する場合は、特別なプロセスと同じようなコードを書いて、特別なタスクを処理するために、コールバックモジュール内の関数を呼ぶようにすればできます。

もし、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