Logger の backend を書いたときに利用した gen_event
について調べたので、その覚書。
gen_event とは
Erlang が標準で提供しているモジュールです。 イベントをハンドリングする仕組みを提供してくれます。
複数のハンドラを登録しておくと、イベントがそれらのハンドラに通知されます。
と、いうわけで。書いてみます。
プロジェクトを用意する
gen_event
を試すプロジェクトを用意します。
アプリケーションの起動時に gen_event
のプロセスを起動したいので --sup
オプションをつけて supervision tree の雛形を生成しておきます。
$ mix new notification --sup
gen_event のプロセスを起動するコードを追加する
lib/notification/application.ex
を編集します。
children
の内容を編集して gen_event
を起動する設定を記述します。
defmodule Notification.Application do @moduledoc false use Application def start(_type, _args) do children = [ %{ id: :gen_event, start: { :gen_event, :start_link, [{:local, Notification}] } } ] opts = [strategy: :one_for_one, name: Notification.Supervisor] Supervisor.start_link(children, opts) end end
start
の値のタプルは、gen_event
のプロセスを起動する関数の呼び出し
:gen_event.start_link({:local, Notification})
を表しています。
iex
を起動して、Notification.Supervisor
が監視しているプロセスの情報を取得すると、gen_event
が起動していることがわかります。
$ iex -S mix iex(1)> Supervisor.which_children(Notification.Supervisor) [{:gen_event, #PID<0.136.0>, :worker, [:gen_event]}]
ハンドラを書く
ハンドラのファイル lib/notification/handler.ex
を追加して gen_event
のコールバックを実装したモジュールを記述していきます。
モジュールのふるまい @behaviour
に :gen_event
を指定します。
defmodule Notification.Handler do @behaviour :gen_event end
この状態でコンパイルすると必要なコールバック関数が実装されていないと警告が表示されます。
$ mix compile Compiling 1 file (.ex) warning: function handle_call/2 required by behaviour :gen_event is not implemented (in module Notification.Handler) lib/notification/handler.ex:1 warning: function handle_event/2 required by behaviour :gen_event is not implemented (in module Notification.Handler) lib/notification/handler.ex:1 warning: function init/1 required by behaviour :gen_event is not implemented (in module Notification.Handler) lib/notification/handler.ex:1 Generated notification app
handle_call/2
, handle_event/2
, init/1
の 3 つの関数が必須なことがわかります。
それ以外には、プロセスの終了時に呼び出される terminate/2
や、 gen_event
以外の要因のメッセージが発生したときに呼び出される handle_info/2
、コードが更新されたときに呼び出される code_change/3
があります。が、今回は最小限で実装します。
defmodule Notification.Handler do @behaviour :gen_event require Logger def init(args) do name = get_in(args, [:name]) Logger.info("#{name} initialized") {:ok, %{name: name}} end def handle_call(request, state) do Logger.info("#{state.name} called with #{request}") {:ok, {:ok, request}, state} end def handle_event(event, state) do Logger.info("#{state.name} received #{event}") {:ok, state} end end
ログを出力するだけの実装です。
ハンドラを登録する
iex
でアプリケーションを起動します。
$ iex -S mix iex(1)>
gen_event
のプロセスは Notification
という名前ですでに起動しているので、ハンドラを登録してみます。
iex(1)> :gen_event.add_handler(Notification, Notification.Handler, name: "Handler1") 22:14:30.726 [info] Handler1 initialized :ok
Handler1
という名前をつけたハンドラが登録されました。
ハンドラに通知する
この状態で通知を送ってみます。
iex(2)> :gen_event.notify(Notification, "Hi") :ok 22:15:24.201 [info] Handler1 received Hi
:gen_event.notify/2
で通知を送ると、handle_event/2
が呼び出されたことがわかります。
:gen_event.call/3
で呼び出すと、handle_call/2
が呼び出されます。
こちらの呼び出しはハンドラのモジュールを指定する必要があります。
同期呼び出しになるので、handle_call/2
が返した値が :gen_event.call/3
の戻り値になります。
:gen_event.call(Notification, Notification.Handler, "Hi") 22:19:38.486 [info] Handler1 called with Hi {:ok, "Hi"}
ハンドラのモジュールの関数を呼び出しているだけのようにも見えますが、ハンドラが登録されていない状態で呼び出すとエラーになります。ハンドラが登録されていないと呼び出せないことがわかります。
$ iex -S mix iex(1)> :gen_event.call(Notification, Notification.Handler, "Hi") {:error, :bad_module}
複数のハンドラを登録し通知する
iex
を起動しなおして、ハンドラを 3 つ名前を変えて登録してみます。
$ iex -S mix iex(1)> :gen_event.add_handler(Notification, Notification.Handler, name: "Handler1") 22:24:54.356 [info] Handler1 initialized :ok iex(2)> :gen_event.add_handler(Notification, Notification.Handler, name: "Handler2") 22:24:56.649 [info] Handler2 initialized :ok iex(3)> :gen_event.add_handler(Notification, Notification.Handler, name: "Handler3") 22:24:58.466 [info] Handler3 initialized :ok
通知を送ります。
iex(4)> :gen_event.notify(Notification, "Hello!") 22:25:47.489 [info] Handler3 received Hello! :ok 22:25:47.489 [info] Handler2 received Hello! 22:25:47.489 [info] Handler1 received Hello!
登録した 3 つのハンドラが呼び出されたことがわかります。ハンドラの実行は非同期ですので、ハンドラのログの出力と :gen_event.notify/2
の戻り値の表示が混ざって表示されています。
イベント源のコードを書く
ハンドラの登録や通知を簡単にするためのコードを書きます。
lib/notification.ex
を編集して :gen_event
の関数の呼び出しを隠す関数を書きます。:gen_event
のプロセスはこのファイルで記述するモジュールの名前 Notification
で登録しているので、__MODULE__
マクロで指定しています。
defmodule Notification do def add_handler(name) do :gen_event.add_handler(__MODULE__, Notification.Handler, name: name) end def notify(event) do :gen_event.notify(__MODULE__, event) end end
実行します。
$ iex -S mix iex(1)> Notification.add_handler("Handler1") 22:30:42.760 [info] Handler1 initialized :ok iex(2)> Notification.add_handler("Handler2") 22:30:44.872 [info] Handler2 initialized :ok iex(3)> Notification.add_handler("Handler3") 22:30:46.296 [info] Handler3 initialized :ok iex(4)> Notification.notify("Hello!") 22:30:59.376 [info] Handler3 received Hello! :ok 22:30:59.376 [info] Handler2 received Hello! 22:30:59.376 [info] Handler1 received Hello!
いつか読むはずっと読まない:ソラリスの陽のもとに
ポーランド語原典からの翻訳版として 国書刊行会 が 2004 年に刊行した単行本を早川書房 が 2015 年に文庫化したもの。
有名な作品ですが、それまではロシア語訳版の翻訳だったんですね。ようやく手にしました。
- 作者: スタニスワフ・レム,岩郷重力,沼野充義
- 出版社/メーカー: 早川書房
- 発売日: 2015/04/08
- メディア: 文庫
- この商品を含むブログ (16件) を見る