OTP Gen Event
Материал из Erlang по-русски.
| Оригинал этой статьи находится по адресу Gen_Event Behaviour |
Содержание |
Поведение Gen_Event
Автор: Mirrorer
Дата: 29.11.2006
Версия: 1.01
Эту главу следует читать вместе с документацией по gen_event(3), там более подробно описаны все интерфейсные функции и функции обратного вызова.
Принципы обработки сообщений
В OTP менеджер событий (event manager) – это именованый объект, которому можно посылать сообщения. Событие(event) может быть, например, ошибкой, нотификацией, или некоторой информацией, которую следует сохранить в логе.
В менеджере событий устанавливается ноль, один или несколько обработчиков событий. Когда менеджер событий получает уведомление о событии, событие обрабатывается всеми установленными обработчиками событий. Например, менеджер событий для обработки ошибок может иметь обработчик по умолчанию, который будет писать сообщения об ошибках на консоль. Если сообщения об ошибках должны быть сохранены в файл в течение определенного периода времени, пользователь добавляет еще один обработчик событий, который будет делать это.
Менеджер событий должен быть реализован как процесс, а каждый обработчик событий должен быть реализован как модуль обратного вызова.
Менеджер событий содержит список пар {Module, State}, где каждый Module – это обработчик событий, а State – внутреннее состояние обработчика событий.
Пример
Модуль обратного вызова для обработчика событий, реализующего вывод сообщений об ошибках на терминал, может выглядеть примерно так :
-module(terminal_logger).
-behaviour(gen_event).
-export([init/1, handle_event/2, terminate/2]).
init(_Args) ->
{ok, []}.
handle_event(ErrorMsg, State) ->
io:format("***Ошибка*** ~p~n", [ErrorMsg]),
{ok, State}.
terminate(_Args, _State) ->
ok.
Модуль обратного вызова для обработчика событий, записывающего сообщения об ошибках в файл, может выглядеть так :
-module(file_logger).
-behaviour(gen_event).
-export([init/1, handle_event/2, terminate/2]).
init(File) ->
{ok, Fd} = file:open(File, read),
{ok, Fd}.
handle_event(ErrorMsg, Fd) ->
io:format(Fd, "***Ошибка*** ~p~n", [ErrorMsg]),
{ok, Fd}.
terminate(_Args, Fd) ->
file:close(Fd).
Этот код объясняется в следующих разделах.
Запуск менеджера событий
Для запуска менеджера событий для обработки ошибок, описанного в примере выше, вызывается следующая функция :
gen_event:start_link({local, error_man})
Эта функция создает новый процесс, менеджер событий, а также создает связь с ним.
Аргумент {local, error_man} обозначает имя. В данном случае менеджер событий будет локально зарегистрирован как error_man.
Если имя будет пропущено, менеджер событий не будет зарегистрирован. В таком случае для общения с ним придется использовать его pid. Имя также может быть зарегистрировано как {global, Name}, в этом случае менеджер событий регистрируется с помощью функции global:register_name/2.
gen_event:start_link может быть использован, если менджер событий является частью дерева контроля, т.е. запускается контролером. Существует другая функция gen_event:start, для запуска менеджера событий в самостоятельном режиме, т.е. для менеджера событий, который не является частью дерева контроля.
Добавления обработчика событий
Ниже приведен пример использования оболочки для запуска менеджера событий и добавления к нему обработчика событий :
1> gen_event:start({local, error_man}).
{ok,<0.31.0>}
2> gen_event:add_handler(error_man, terminal_logger, []).
ok
Эта функция посылает сообщение менеджеру событий, зарегистрированному как error_man, приказывая ему добавить обработчик событий terminal_logger. Менеджер событий вызовет функцию обратного вызова terminal_logger:init([]), в которой аргумент [] является третьим аргументом для функции add_handler. init должна вернуть {ok, State}, где State – внутреннее состояние обработчика событий.
init(_Args) ->
{ok, []}.
В данном случае, функции init не нужны какие-либо внутренние данные, и она игнорирует аргумент. Также внутреннее состояние не используется для функции terminal_logger. Для функции file_logger внутреннее состояние используется для сохранения дескриптора открытого файла.
init(_Args) ->
{ok, Fd} = file:open(File, read),
{ok, Fd}.
Уведомление о событиях
3> gen_event:notify(error_man, no_reply). ***Ошибка*** no_reply ok
error_man – это имя менеджера событий, а no_reply – это событие.
Событие преобразовывается в сообщение и посылается менеджеру событий. Когда событие получено, менеджер событий вызывает handle_event(Event, State) для каждого установленного обработчика событий, в том порядке, в каком они добавлялись. Эта функция должна вернуть кортеж {ok, State1}, где State1 – новое состояние обработчика событий.
В terminal_logger-е:
handle_event(ErrorMsg, State) ->
io:format("***Error*** ~p~n", [ErrorMsg]),
{ok, State}.
В file_logger-е:
handle_event(ErrorMsg, Fd) ->
io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
{ok, Fd}.
Удаление обработчика событий
4> gen_event:delete_handler(error_man, terminal_logger, []). ok
Функция посылает сообщение менеджеру событий, зарегистрированному как error_man, приказывая ему удалить обработчик событий terminal_logger. Менджер событий вызовает функцию обратного вызова terminal_logger:terminate([], State), где аргумент [] – это третий аргумент функции delete_handler. terminate должен быть «антонимом» init и выполнять необходимую очистку ресурсов. Значение, возвращаемое этим методом, игнорируется.
Для terminal_logger-а освобождение ресурсов необязательно:
terminate(_Args, _State) -> ok.
Для file_logger-а файл, открытый в init, должен быть закрыт:
terminate(_Args, Fd) -> file:close(Fd).
Завершение работы
В дереве контроля
Если менджер событий является частью дерева контроля, функция завершения работы не требуется. Менеджер событий будет автоматически завершен контролером. Как именно это будет сделано, определяется стратегией завершения (shutdown strategy), установленной в контролере.
Самостоятельные менеджеры событий
Менеджер событий может быть остановлен следующим вызовом :
> gen_event:stop(error_man). ok


