> ## 5 Events

> The event manager behaviour `gen_event` provides a general framework for building application specific event handling routines.

> Refer to the Reference Manual , the module `gen_event` in `stdlib`, for full details of the behaviour interface.

> Event managers provide named objects to which events can be sent. When an event arrives at an event manager, it will be processed by all the event handlers which have been installed within the event manager. None or several event handlers can be installed within a given event manager.

> Event handlers can be written which act on all events in a particular class, on some of the events, or on some particular complex combination of events.

> All events are processed by functions which are called from the module `gen_event` .

> Event managers can be manipulated at runtime. In particular, we can install an event handler, remove an event handler, or replace one event handler with a different handler.

> Event managers can be built for tasks like:

> - error logging
> - alarm handling
> - call record logging
> - debugging
> - equipment management.

> The event mechanism provides an extremely powerful model for building a large number of different applications. The following sections include examples of the kind of applications which can be built.

> ### 5.1 Definitions

> The following definitions will help in understand this topic.

> Event
>   An occurrence, or something which happens.
> Event Category
>   The type or class of an event.
> Event Manager
>   A process which coordinates the processing of events of the same category.
> Notification
>   The act of informing an event manager that an event has occurred.
> Event Handler
>   A module which exports functions that can process events of a particular category. Event handlers can be installed within an event manager.

> ### 5.2 The Event Manager

> The event manager essentially maintains a list of `{Mod, State}` pairs, which are called an MS list. For example:

>     [{Mod1, State1}, {Mod2, State2}, ...]

> New modules are added to this list by calling `gen_event:add_handler(EventManager, NewMod, Args)`.

> `EventManager` is the name of the event manager, and `NewMod` is the name of an event handler and its callback module.

> The event manager calls `NewMod:init(Args)`, which is expected to return `{ok, NewState}`. If this happens, the tuple `{NewMod, NewState}` is added to the MS list.

> When an application generates an event by calling `gen_event:notify(EventManager, Event)`, the event `Event` is delivered to the event manager.

> The event manager then processes the event by calling `Mod:handle_event(Event, State)` for each module in the MS list. This has the effect of replacing the MS list `[{Mod1, State1}, {Mod2, State2}, ....]` with `[{Mod1, State1p}, {Mod2, State2p}, ...]`, where:

>     {ok, State1p} = Mod1:handle_event(Event, State1)
>     {ok, State2p} = Mod2:handle_event(Event, State2)

> The event manager can be thought of as a generalization of a conventional finite state machine. Instead of a single state, we maintain a set of states, and a set of state transition functions.

> We further generalize this mechanism by allowing `handle_event` to return not only a new state, but also by allowing it to request a change of the event handler, or to request the removal of the existing event handler. What happens is shown by the following pseudo-code example which executes within `gen_event`. The callback functions `Mod1:terminate(...)` and `Mod2:init(...)` must also be supplied by the user.

>     notify(Event, Mod1, State) ->
>          case Mod1:handle_event(Event, State) of
>               {ok, State1} ->
>                    ... add  {Mod1, State1} to the MS list
>               remove_handler ->
>                    Mod1:terminate(remove_handler, State),
>                    ... delete the handler from the MS list
>               {swap_handler, Args1, State1, Mod2, Args2}
>                    State2 = Mod1:terminate(Args1, State1),
>                    {ok, State2a} = Mod2:init({Args2, State2}),
>                    ... add {Mod2, State2a} to the MS list and delete
>                                                     the Mod1 handler

> The handler returns the following values:

> - `{ok, State}`. The new state is added to the MS list.
> - `remove_handler`. The handler is finalized and then removed from the MS list.
> - `{swap_handler, ...}`. The handler is finalized and the return value is passed into the `init` function of the new handler.

> You can also send a request to a specific handler in the MS list by evaluating `gen_event:call(EventManager, Mod, Query)`, which returns the value obtained by evaluating `Mod:handle_call(Query, State)`.

> You remove a handler with the call `gen_event:delete_handler(EventManager, Mod, Args)`, which returns the value obtained by evaluating `Mod:terminate(Args, State)`, where `State` is the state associated with `Mod` in the MS list.

> #### 5.2.1 Finalization

> Each time a new handler is installed, `Mod:init(...)` is called, and each time a handler is removed `Mod:terminate(...)` is called.

> The act of calling a specific routine every time a handler is removed is called "finalization". The finalization routine `terminate` has two arguments:

> - `Args` which is the reason for termination
> - `State` which is the current value of the state.

> `Mod:terminate/2` is expected to return a new state. Depending on the context, this state is sometimes ignored and sometimes passed into a new initialization routine.

> ### 5.3 Writing an Event Manager

> To create a new event manager, we evaluate the function `gen_event:start(Manager)`, where `Manager` is the name of the event manager.

> For example, the call `gen_event:start({local, error_logger})` starts a new (local) event manager called `error_logger`. Note that calling `gen_event:start({local, Manager})` has the side effect of creating a new registered process named `Manager`.

> | We could also create a global event manager by calling `gen_event:start({global, event_logger})`.

> So far, the error logger cannot do anything and we have to install a handler. The function `gen_event:add_handler(Manager, Handler, Args)` can be used to install the handler `Handler` in the event manager `Manager`.

> When `gen_event:add_handler(Manager, Handler, Args)` is called, the event manager calls the function `Handler:init(Args)` which normally returns `{ok,State}`. The value of `State` is stored in the event manager together with the name of the handler.

> Any process can send an event to the event manager by evaluating the function `gen_event:notify(Manager, Event)`. When this happens, the event manager processes the event by calling the function `Handler:handle_event(Event, State)`. This is done for each handler which has been installed in the manager. The function `Handler:handle_event(Event, State)` should return one of three different values:

> - `{ok, State}`. `State` is a new state which will be used the next time the handler is called.
> - `remove_handler`. This tells the event manager to remove this event handler by calling `Handler:terminate(remove_handler, State)`.
> - `{swap_handler, Args1, State1, Mod2, Args2}`. This tells the event manager to remove the current event handler by calling `State2 = Handler:terminate(Args1, State1)`, and then to install a new handler by calling `Mod2:init(Args2, State2)`

> #### 5.3.1 An Error Logger

> The module `error_logger_memory_h` provides a simple memory resident error logger. It stores at most `Max` error messages. After this all error messages are lost.

>     -module(error_logger_memory_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/roma/2 $ ').

>     -behaviour(gen_event).

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

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

>     handle_event(Event, {1, Lost, Buff}) ->
>         {ok, {1, Lost+1, Buff}};
>     handle_event(Event, {N, Lost, Buff}) ->
>         {ok, {N-1, Lost, [{event1, date(), time(), Event}|Buff]}}.

>     handle_info(_, S) -> {ok, S}.

>     handle_call(_, S) -> {ok, ok, S}.

>     terminate(swap_to_file, {_, 0, Buff}) ->
>         {error_logger_memory_h, Buff};
>     terminate(swap_to_file, {_, Lost, Buff}) ->
>         {error_logger_memory_h,
>           [{event1,date(),time(),{Lost, messages_lost}}|Buff]};
>     terminate(_, State) ->
>         ... display the data using a secret internal BIF ...
>         ...

> To start a simple memory based error logger which can store at most 25 messages we evaluate:

>     gen_event:start({local, error_logger}),
>     gen_event:add_handler(error_logger, error_logger_memory_h, 25).

> To log an error, an application evaluates the expression:

>     gen_event:notify(error_logger, Event)

> This error logger is similar to the error logger installed in the system kernel when the system boots. Be aware that no file system has been installed just after the system has started, so if any errors occur they are stored in memory. This error logger is perfectly adequate for recording errors which occur when booting the system.

> The simple error logger shown can be improved by doing something more intelligent with the errors.

> - If a file system is installed, then we might want to store the errors in disk files.
> - If we have no file system, but we are a distributed Erlang node, then we might wish to send the errors to some other node in the system where they can be stored.

> The following example shows a handler which stores events on disk:

>     -module(error_logger_file_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/3 $').

>     -behaviour(gen_event).

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

>     init({{Fname,Max,N}, {error_logger_memory_h, Buff}}) ->
>          {ok, {{Fname, N}, length(Buff), Max, Buff}}.

>     handle_event(Event, {F, N, Max, Buff}) ->
>          Buff1 = [{event1, date(), time(), Event}|Buff],
>          N1 = N + 1,
>          if
>              N1 > Max -> {ok, {dump_events(F, Buff1), 0, Max, []}};
>              true     -> {ok, {F, N1, Max, Buff1}}
>          end.

>     handle_info(_, S) -> {ok, S}.

>     handle_call(_, S) -> {ok, ok, S}.

>     terminate(_, {F, N, Max, Buff}) ->
>         dump_events(F, Buff),
>         ok.

>     dump_events(F, []) ->F;
>     dump_events({File, Index}, Buff) ->
>         Fname = File ++ integer_to_list(Index) ++ ".log",
>         file:write_file(Fname, term_to_binary(Buff)),
>         {File, Index + 1}.

> This handler has been explicitly written to take over from the simple error handler. To swap handlers so that all errors are logged on disk we can evaluate:

>     gen_event:swap_handler(error_logger,
>                           {error_logger_memory_h, swap_to_file},
>                            {file_error_handler_h, {"/usr/local/file/log",100,45}}).

> Each disk file will contain 100 events. These files will be called `/usr/local/file/log45.log`,`/usr/local/file/log46.log`, and so on.

> The reader should also examine this example carefully and observe the flow of control between the finalization routine in the memory resident error logger, and the initialization routine in the file logger.

> #### 5.3.2 An Alarm Handler

> We start by creating an alarm manager.

>     gen_event:start({local, alarm}).

> That is all you need to do to make an alarm manager. Any process can now generate an alarm by evaluating `gen_event:notify(alarm, Event)`.

> For example, to say that apparatus one is overheating you might call:

>     gen_event:notify(alarm, {hardware, 1, overheating}).

> This alarm is then delivered to the alarm manager. However, the alarm manager will ignore the alarm since no alarm handlers have been installed.

> The following example shows how to write and install an alarm handler which sends all alarms to the error logger:

>     -module(log_all_alarms_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/2 $').

>     -behaviour(gen_event).

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

>     init(_) -> {ok, 1}.

>     handle_event(Event, N) ->
>         gen_event:notify(error_logger, {alarm, N, Event}),
>         {ok, N + 1}.

>     handle_info(_, S) -> {ok, S}.

>     handle_call(_,S) -> {ok, ok, S}.

>     terminate(_, _) -> ok.

> The next example shows an alarm handler which is only interested in alarms from hardware1. This handler counts the alarms and stops the hardware if 10 alarms have arrived:

>     -module(hardware_1_alarms_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/roma/1 $').

>     -behaviour(gen_event).

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

>     init(_) -> {ok, 1}.

>     handle_event({hardware, 1, What}, N) ->
>          N1 = N + 1,
>          if
>              N1 == 10 ->
>                  %% .... code to stop hardware 1 ....
>                  {ok, true};
>              true ->
>                  {ok, N + 1}
>          end;
>     handle_event(_,State) ->
>         %% This catches all other events intended
>         %% for other handlers
>         {ok, State}.

>     handle_call(_,State) -> {ok, ok, State}.

>     terminate(_, _) -> ok.

> Both of these alarm handlers are installed in the alarm manager as follows:

>     gen_event:add_handler(alarm, log_all_alarms_h, []),
>     gen_event:add_handler(alarm, hardware_1_alarm_h, []),

> Both handlers will run concurrently. A specialized handler can be added and removed at any time. Note also the second clause of `handle_event`. Since our handler must succeed for any event we add a final "catch all" clause and make sure it returns the original state.

> #### 5.3.3 Exit Notification

> This section describes how to monitor a process and send a message to the error logger if the process terminates with an abnormal exit.

>     -module(at_exit_log_error_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/roma/1 $ ').

>     -behaviour(gen_event).

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

>     init(_) ->
>         process_flag(trap_exit, true),
>         {ok, []}.

>     handle_event({monitor, Pid}, S) ->
>         link(Pid), {ok, S};
>     handle_event(_, S) ->
>         {ok, S}.

>     handle_info({'EXIT', _, normal}, S) ->
>         {ok, S};
>     handle_info({'EXIT', Pid, Why}, S) ->
>         gen_event:notify(error_logger, {non_normal_exit, Pid, Why}),
>         {ok, S};
>     handle_info(_, S) -> {ok, S}.

>     handle_call(_, S) -> {ok, ok, S}.

>     terminate(_, _) -> ok.

> To start the handler we evaluate:

>     gen_event:start({local, at_exit}),
>     gen_event:add_handler(at_exit, at_exit_log_error_h, []).

> A monitoring process is started with `gen_event:notify(at_exit, {monitor, Pid}).` where `Pid` represents the process that we wish to monitor.

> #### 5.3.4 Exit Handler

> This section describes how to trigger an event to occur when a process exits.

>     -module(at_exit_apply_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/roma/1 $ ').

>     -behaviour(gen_event).

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

>     init(_) ->
>         process_flag(trap_exit, true),
>         {ok, []}.

>     handle_event({at_exit_apply,Pid,MFA},S) -> {ok, [{Pid, MFA}|S]};
>     handle_event(_, S)                      -> {ok, S}.

>     handle_info({'EXIT', Pid, _}, S) -> {ok, do_exit_actions(Pid,S,[])};
>     handle_info(_, S) -> {ok, S}.

>     handle_call(_, S) -> {ok, ok, S}.

>     terminate(_, _) -> [].

>     do_exit_actions(Pid, [{Pid, {M,F,A}}|T], L) ->
>         catch apply(M, F, A),
>         do_exit_actions(Pid, T, L);
>     do_exit_actions(Pid, [H|T], L) ->
>         do_exit_actions(Pid, T, [H|L]);
>     do_exit_actions(Pid, [], L) ->
>         L.

> Install the handler as follows:

>     gen_event:start({local, at_exit}).
>     gen_event:add_handler(at_exit, at_exit_apply_h, []).

> Set an event as follows:

>     gen_event:notify(at_exit, {at_exit, Pid, MFA})

> Now, whenever `Pid` dies, `MFA` will be applied.

> ### 5.4 One or Many Handlers

> The previous sections describe three different `at_exit` handlers. When designing a system we have to decide whether to install three different handlers in the same manager, or to create three different managers each with a single handler.

> The following two examples produce the same effect.

> Example 1:

>     gen_event:start({local, at_exit}).
>     gen_event:add_handler(at_exit, at_exit_apply_h, []).
>     gen_event:add_handler(at_exit, at_exit_log_error_h, []).
>     ...
>     gen_event:notify(at_exit, {monitor, Pid}).
>     gen_event:notify(at_exit, {at_exit_apply, Pid, MFA}).

> Example 2:

>     gen_event:start({local, at_exit_apply}).
>     gen_event:add_handler(at_exit_apply, at_exit_apply_h, []).
>     gen_event:start({local, at_exit_log_error}).
>     gen_event:add_handler(at_exit_log_error, at_exit_log_error_h, []).
>     ...
>     gen_event:notify(at_exit_apply, {monitor, Pid}).
>     gen_event:notify(at_exit_log_error, {at_exit_apply, Pid, MFA}).

> The first example creates one manager and installs two handlers. The second example creates two managers each with a single handler.

> The first strategy is more flexible and will allow more handlers to be added at runtime, but at the cost of reducing concurrency in the system.

> #### 5.4.1 Plug and Play

> This example assumes that a number of hardware drivers have been written which can automatically detect when hardware is added or removed from a system. The following functions are used to add and remove hardware from the system:

> - `gen_event:notify(plug_and_play, {added, Hw}).` Use this function to add hardware.
> - `gen_event:notify(plug_and_play, {removed, Hw}).` Use this function to remove hardware.

> The handler `plug_and_play_db_h` maintains a database of all plug and play hardware which has been added to the system:

>     -module(plug_and_play_db_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/roma/1 $').

>     -behaviour(gen_event).

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

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

>     handle_event({added, Hw}, S)   -> {ok, [Hw|S]};
>     handle_event({removed, Hw}, S) -> {ok, lists:delete(Hw, S)};
>     handle_event(_, S)             -> {ok, S}.

>     handle_info(_, S) -> {ok, S}.

>     handle_call(what_hardware, State) -> {ok, State, State}.

>     terminate(_, _) -> ok.

> This code just keeps a record of all hardware that has been started in a list. You can ask what hardware has been installed by evaluating the following function, which returns a list of the hardware that the plug and play manager knows about.

>     gen_event:call(plug_and_play, plug_and_play_db_h, what_hardware)

> The following example shows a specialized handler which serves the purpose of doing something special when a piece of hardware is added, and doing something different when this piece of hardware is removed. The example is written for a sound card:

>     -module(plug_and_play_sound_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/2 $').

>     -behaviour(gen_event).

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

>     init(_) -> {ok, none}.

>     handle_event({added, {soundcard, X}}, S) ->
>         Pid = soundcard:start(X),
>         {ok, [{X, Pid}|S]};
>     handle_event({removed, {soundcard, X}}, S) ->
>         {ok, stop_card(X, S, [])};
>     handle_event(_, S) ->
>         {ok, S}.

>     stop_card(X, [{X, Pid}|T], L) -> soundcard:stop(Pid), lists:reverse(L, T);
>     stop_card(X, [H|T], L)        -> stop_card(X, T, [H|L]);
>     stop_card(X, [], L)           -> L.

>     handle_info(_, S) -> {ok, S}.

>     handle_call(_,S) -> {ok, ok, S}.

>     terminate(_,_) -> ok.

> The plug-and-play manager can take care of both the plug-and-play database and the special processing of sound cards, when they are added and removed.

>     gen_event:start({local, plug_and_play}).
>     gen_event:add_handler(plug_and_play, plug_and_play_db_h, []).
>     gen_event:add_handler(plug_and_play, plug_and_play_sound_h, []).

> #### 5.4.2 Trace Logger

> This section describes a simple handler which can trace all "foo" events.

>     -module(trace_foo_h).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/roma/1 $').

>     -behaviour(gen_event).

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

>     init(File) ->
>         {ok, Stream} = file:open(File, write),
>         {ok, Stream}.

>     handle_event({foo, X}, F) -> io:format(F, "~w~n", [X]), {ok, F};
>     handle_event(_,S)         -> {ok, S}.

>     handle_info(_, S) -> {ok, S}.

>     handle_call(_,S) -> {ok, ok, S}.

>     terminate(_, S) -> file:close(S), ok.

> If you start the tracer with the function `gen_event:start({local, tracer}).` and trace "foo" events with the call `gen_event:notify(tracer, {foo, ...}).`, nothing will happen.

> If you install a trace handler by calling `gen_event:add_handler(tracer, trace_foo_h, "/usr/local/file1").`, then all foo events will be written to the file "`/usr/local/file1`".

> Evaluating `gen_event:remove_handler(tracer, trace_foo_h).` removes the handler and closes the file at the same time .

> | This example supplies arguments to both `init` and `terminate`.

> ### 5.5 Encapsulation

> In all the examples shown in this section, `gen_event` function calls have been used instead of encapsulating the different functions which access the event manager. In the following example, the interface routines `start/0`, `stop/0`, `added/1`, `removed/1` and `which/0` are added to the code for `plug_and_play.erl`.

>     -module(plug_and_play).
>     -copyright('Copyright (c) 1991-97 Ericsson Telecom AB').
>     -vsn('$Revision: /main/release/2 $').

>     -behaviour(gen_event).

>     -export([start/0, stop/0, added/1, removed/1, which/0]).
>     -export([init/1, handle_event/2, handle_call/2, terminate/2]).

>     start() ->
>         gen_event:start({local, plug_and_play}),
>         gen_event:add_handler(plug_and_play, plug_and_play, []).

>     stop()      -> gen_event:stop(plug_and_play).
>     added(Hw)   -> gen_event:notify(plug_and_play, {added, Hw}).
>     removed(Hw) -> gen_event:notify(plug_and_play, {removed, Hw}).
>     which()     -> gen_event:call(plug_and_play, plug_and_play, what_hardware).

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

>     handle_event({added, Hw}, S)      -> {state, [Hw|S]};
>     handle_event({removed_hw, Hw}, S) -> {state, lists:delete(Hw, S)};
>     handle_event(_, S)                -> {state, S}.

>     handle_call(what_hardware, State) -> {ok, State, State}.

>     terminate(_, _) -> ok.

> This module should now be accessed through its interface routines only, and all details on how it was implemented using `gen_event` can be omitted.

Copyright © 1991-2003 Ericsson Utvecklings AB