gen_event

イベント発生時の処理を管理する為のモジュール。
管理プロセスに、予め処理を 0〜複数個 登録しておくと、イベント発生時に実行してくれる。

管理プロセスの起動

1> gen_event:start_link({local, event_hoge}).
{ok,<0.33.0>}

これで、event_hoge という名前の管理プロセスが立ち上がる。

管理プロセスの停止

2> gen_event:stop(event_hoge).
ok

管理プロセスにイベントハンドラを登録

まず、イベントハンドラを作る。

-module(event_hoge_handler1).
-author('cooldaemon@gmail.com').

-export([
  init/1,
  handle_event/2, handle_call/2, handle_info/2,
  terminate/2
]).

init(_Args) -> {ok, []}.

handle_event(Msg, State) ->
  io:format("~p: ~p~n", [?MODULE, Msg]),
  {ok, State}.
  
handle_call(_Args, State) -> {ok, {error, bad_query}, State}.
handle_info(_Args, State) -> {ok, State}.
terminate(_Args, _State)  -> ok.

ついでに、同じ処理内容の名前だけ違うイベントハンドラも用意しておく。

-module(event_hoge_handler2).
-author('cooldaemon@gmail.com').

-export([
  init/1,
  handle_event/2, handle_call/2, handle_info/2,
  terminate/2
]).

init(_Args) -> {ok, []}.

handle_event(Msg, State) ->
  io:format("~p: ~p~n", [?MODULE, Msg]),
  {ok, State}.
  
handle_call(_Args, State) -> {ok, {error, bad_query}, State}.
handle_info(_Args, State) -> {ok, State}.
terminate(_Args, _State)  -> ok.

作ったイベントハンドラを登録する。(事前に、管理プロセスを起動しておく事)

1> gen_event:start_link({local, event_hoge}).
{ok,<0.33.0>}
2> lists:foreach(fun (Handler) -> gen_event:add_handler(event_hoge, Handler, []) end, [event_hoge_handler1, event_hoge_handler2]).
ok

試しに使ってみる。

3> gen_event:notify(event_hoge, test).
ok
event_hoge_handler2: test
event_hoge_handler1: test

イベントハンドラ内でエラーが発生した場合

わざとに badmatch が発生するイベントハンドラを作る。

-module(event_hoge_handler_fail).
-author('cooldaemon@gmail.com').

-export([
  init/1,
  handle_event/2, handle_call/2, handle_info/2,
  terminate/2
]).

init(_Args) -> {ok, []}.

handle_event(Msg, State) ->
  1 = 2,
  {ok, State}.
  
handle_call(_Args, State) -> {ok, {error, bad_query}, State}.
handle_info(_Args, State) -> {ok, State}.
terminate(_Args, _State)  -> ok.

登録して使ってみる。

3> gen_event:add_handler(event_hoge, event_hoge_handler_fail, []).
ok
4> gen_event:notify(event_hoge, test).
ok
=ERROR REPORT==== 7-Dec-2007::15:58:37 ===
** gen_event handler event_hoge_handler_fail crashed.
** Was installed in event_hoge
** Last event was: test
** When handler state == []
** Reason == {{badmatch,2},
              [{event_hoge_handler_fail,handle_event,2},
               {gen_event,server_notify,4},
               {gen_event,handle_msg,5},
               {proc_lib,init_p,5}]}
event_hoge_handler2: test
event_hoge_handler1: test
5> gen_event:notify(event_hoge, test).
ok
event_hoge_handler2: test
event_hoge_handler1: test

gen_server と違い、gen_event によって起動された管理プロセスは、コールバック関数内でエラーが発生しても停止しない。
エラー発生後、gen_event:notify/2 を実行すると、イベントハンドラが除外されているのが解る。

イベントハンドラ内のエラーを呼び出し元に通知させる

gen_event:add_sup_handler/3 を使うと、イベントハンドラでエラーが発生した場合、呼び出し元のプロセスに {gen_event_EXIT,Handler,Reason} というメッセージを通知してくれる。

メッセージ {gen_event_EXIT,Handler,Reason} を受け取る gen_server の例

-module(handler_guard).
-author('cooldaemon@gmail.com').
-behavior(gen_server).

-export([start_link/1]).
-export([
  init/1,
  handle_cast/2, handle_call/3, handle_info/2,
  terminate/2, code_change/3
]).

start_link(HandlerModule) ->
  gen_server:start_link({local, ?MODULE}, ?MODULE, [HandlerModule], []).

init([HandlerModule]) ->
  case gen_event:add_sup_handler(event_hoge, HandlerModule, []) of
    ok    -> {ok, HandlerModule};
    Error -> {stop, Error}
  end.

handle_cast(_, HandlerModule) ->
  {noreply, HandlerModule}.

handle_call(_, _, HandlerModule) ->
  {reply, not_call, HandlerModule}.

handle_info({gen_event_EXIT, HandlerModule, Reason}, HandlerModule) ->
  io:format(
    "~w: detected handler ~p shutdown:~n~p~n",
    [?MODULE, HandlerModule, Reason]
  ),
  {noreply, HandlerModule};

handle_info(_, HandlerModule) ->
  {noreply, HandlerModule}.

terminate(_, _) -> ok.
code_change(_, _, HandlerModule) -> {ok, HandlerModule}.  

実際に試してみる。

1> gen_event:start_link({local, event_hoge}).
{ok,<0.33.0>}
2> handler_guard:start_link(event_hoge_handler_fail).
{ok,<0.40.0>}
3> gen_event:notify(event_hoge, test).
ok
handler_guard: detected handler event_hoge_handler_fail shutdown:
{'EXIT',{{badmatch,2},
         [{event_hoge_handler_fail,handle_event,2},
          {gen_event,server_notify,4},
          {gen_event,handle_msg,5},
          {proc_lib,init_p,5}]}}
=ERROR REPORT==== 11-Dec-2007::13:29:01 ===
** gen_event handler event_hoge_handler_fail crashed.
** Was installed in event_hoge
** Last event was: test
** When handler state == []
** Reason == {{badmatch,2},
              [{event_hoge_handler_fail,handle_event,2},
               {gen_event,server_notify,4},
               {gen_event,handle_msg,5},
               {proc_lib,init_p,5}]}

管理プロセスからイベントハンドラを削除

6> gen_event:delete_handler(event_hoge, event_hoge_handler1, []).
ok

管理プロセスに登録済みのイベントハンドラを交換

7> gen_event:swap_sup_handler(event_hoge, {event_hoge_handler2, swap}, {event_hoge_handler3, []}).
ok

event_hoge_handler3 の内容は割愛。

OTP でよく使う error_logger にイベントハンドラを追加

-module(my_error_handler).
-author('cooldaemon@gmail.com').

%% API
-export([start/0, stop/0]).

%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2, handle_info/2, terminate/2]).

start() ->
  case lists:member(?MODULE, gen_event:which_handlers(error_logger)) of
    true  ->
      already_started;
    false ->
      case gen_event:add_handler(error_logger, ?MODULE, []) of
        ok -> 
          ok;
        {error, Reason} ->
          throw({error, {?MODULE, start_link, Reason}})
      end
  end.

stop() ->
  gen_event:delete_handler(error_logger, ?MODULE, []).

init(_Args) -> {ok, []}.

handle_event(ErrorMsg, State) ->
  io:format("***DEBUG*** ~p~n", [ErrorMsg]),
  {ok, State}.
  
handle_call(_Query, State) -> {ok, {error, bad_query}, State}.
handle_info(_, State)      -> {ok, State}.
terminate(_Args, _State)   -> ok.

gen_event:which_handlers/1 で、イベントハンドラが既に登録済みかどうかチェックしている所がミソ。