A behaviour module for implementing event handling functionality.
The event handling model consists of a generic event manager process with an arbitrary number of event handlers which are added and deleted dynamically.
An event manager implemented using this module will have a standard set of interface functions and include functionality for tracing and error reporting. It will also fit into an supervision tree.
Example
There are many use cases for event handlers. For example, a logging system can be built using event handlers where each log message is an event and different event handlers can be plugged to handle the log messages. One handler may print error messages on the terminal, another can write it to a file, while a third one can keep the messages in memory (like a buffer) until they are read.
As an example, let's have a GenEvent that accumulates messages until they are collected by an explicit call.
defmodule LoggerHandler do
use GenEvent
# Callbacks
def handle_event({:log, x}, messages) do
{:ok, [x|messages]}
end
def handle_call(:messages, messages) do
{:ok, Enum.reverse(messages), []}
end
end
{:ok, pid} = GenEvent.start_link()
GenEvent.add_handler(pid, LoggerHandler, [])
#=> :ok
GenEvent.notify(pid, {:log, 1})
#=> :ok
GenEvent.notify(pid, {:log, 2})
#=> :ok
GenEvent.call(pid, LoggerHandler, :messages)
#=> [1, 2]
GenEvent.call(pid, LoggerHandler, :messages)
#=> []
We start a new event manager by calling GenEvent.start_link/0
.
Notifications can be sent to the event manager which will then
invoke handle_event/2
for each registered handler.
We can add new handlers with add_handler/4
. Calls can also
be made to specific handlers by using call/3
.
Callbacks
There are 6 callbacks required to be implemented in a GenEvent
. By
adding use GenEvent
to your module, Elixir will automatically define
all 6 callbacks for you, leaving it up to you to implement the ones
you want to customize. The callbacks are:
init(args)
- invoked when the event handler is added.It must return:
-
{:ok, state}
-
{:ok, state, :hibernate}
-
{:error, reason}
-
handle_event(msg, state)
- invoked whenever an event is sent vianotify/2
,ack_notify/2
orsync_notify/2
.It must return:
-
{:ok, new_state}
-
{:ok, new_state, :hibernate}
-
{:swap_handler, args1, new_state, handler2, args2}
-
:remove_handler
-
handle_call(msg, state)
- invoked when acall/3
is done to a specific handler.It must return:
-
{:ok, reply, new_state}
-
{:ok, reply, new_state, :hibernate}
-
{:swap_handler, reply, args1, new_state, handler2, args2}
-
{:remove_handler, reply}
-
handle_info(msg, state)
- invoked to handle all other messages which are received by the process. Must return the same values ashandle_event/2
.terminate(reason, state)
- called when the event handler is removed or the event manager is terminating. It can return any term.The reason is one of:
-
:stop
- manager is terminating -
{:stop, reason}
- monitored process terminated (for monitored handlers) -
:remove_handler
- handler is being removed -
{:error, term}
- handler crashed or returned a bad value -
term
- any term passed to functions likeGenEvent.remove_handler/2
-
code_change(old_vsn, state, extra)
- called when the application code is being upgraded live (hot code swapping).It must return:
-
{:ok, new_state}
-
Name Registration
A GenEvent is bound to the same name registration rules as a GenServer
.
Read more about it in the GenServer
docs.
Modes
GenEvent stream supports three different notifications.
On GenEvent.ack_notify/2
, the manager acknowledges each event,
providing back pressure, but processing of the message happens
asynchronously.
On GenEvent.sync_notify/2
, the manager acknowledges an event
just after it was processed by all event handlers.
On GenEvent.notify/2
, all events are processed asynchronously and
there is no ack (which means there is no backpressure).
Streaming
GenEvent
s can be streamed from and streamed with the help of stream/2
.
Here are some examples:
stream = GenEvent.stream(pid)
# Take the next 10 events
Enum.take(stream, 10)
# Print all remaining events
for event <- stream do
IO.inspect event
end
A stream may also be given an id, which allows all streams with the given
id to be cancelled at any moment via cancel_streams/1
.
Learn more and compatibility
If you wish to find out more about gen events, Elixir getting started guides provide a tutorial-like introduction. The documentation and links in Erlang can also provide extra insight.
- http://elixir-lang.org/getting_started/mix/1.html
- http://www.erlang.org/doc/man/gen_event.html
- http://learnyousomeerlang.com/event-handlers
Keep in mind though Elixir and Erlang gen events are not 100% compatible.
The :gen_event.add_sup_handler/3
is not supported by Elixir's GenEvent,
which in turn supports monitor: true
in GenEvent.add_handler/4
.
The benefits of the monitoring approach are described in the "Don't drink too much kool aid" section of the "Learn you some Erlang" link above. Due to those changes, Elixir's GenEvent does not trap exits by default.
Futhermore, Elixir's also normalizes the {:error, _}
tuples returned
by many functions, in order to be more consistent with themselves and
the GenServer
module.