Elixir v1.0.5 GenEvent
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/3
. 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}
: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}
{: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 valueterm
- 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 GenEvent.add_mon_handler/3
.
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.
Summary
Types
Supported values for new handlers
The event manager reference
The GenEvent manager name
Return values of start*
functions
Options used by the start*
functions
Functions
Sends a ack event notification to the event manager
Adds a new event handler to the event manager
Adds a monitored event handler to the event manager
Makes a synchronous call to the event handler
installed in manager
Sends an event notification to the event manager
Removes an event handler from the event manager
Starts an event manager process without links (outside of a supervision tree)
Starts an event manager linked to the current process
Terminates the event manager
Returns a stream that consumes events from the manager
Replaces an old event handler with a new one in the event manager
Replaces an old event handler with a new monitored one in the event manager
Sends a sync event notification to the event manager
Returns a list of all event handlers installed in the manager
Types
Functions
Specs
ack_notify(manager, term) :: :ok
Sends a ack event notification to the event manager
.
In other words, this function only returns :ok
as soon as the
event manager starts processing this event, but it does not wait
for event handlers to process the sent event.
See notify/2
for more info. Note this function is specific
to Elixir’s GenEvent and does not work with Erlang ones.
Adds a new event handler to the event manager
.
The event manager will call the init/1
callback with args
to
initiate the event handler and its internal state.
If init/1
returns a correct value indicating successful completion,
the event manager adds the event handler and this function returns
:ok
. If the callback fails with reason
or returns {:error, reason}
,
the event handler is ignored and this function returns {:error, reason}
.
If the given handler was previously installed at the manager, this
function returns {:error, :already_present}
.
Adds a monitored event handler to the event manager
.
Expects the same input and returns the same values as add_handler/3
.
Monitored handlers
A monitored handler implies the calling process will now be monitored by the GenEvent manager.
If the calling process later terminates with reason
, the event manager
will delete the event handler by calling the terminate/2
callback with
{:stop, reason}
as argument. If the event handler later is deleted,
the event manager sends a message {:gen_event_EXIT, handler, reason}
to the calling process. Reason is one of the following:
:normal
- if the event handler has been removed due to a call toremove_handler/3
, or:remove_handler
has been returned by a callback function:shutdown
- if the event handler has been removed because the event manager is terminating{:swapped, new_handler, pid}
- if the process pid has replaced the event handler by another- a term - if the event handler is removed due to an error. Which term depends on the error
Keep in mind that the {:gen_event_EXIT, handler, reason}
message is not
guaranteed to be delivered in case the manager crashes. If you want to
guarantee the message is delivered, you have two options:
- monitor the event manager
- link to the event manager and then set
Process.flag(:trap_exit, true)
in your handler callback
Finally, this functionality only works with GenEvent started via this
module (it is not backwards compatible with Erlang’s :gen_event
).
Makes a synchronous call to the event handler
installed in manager
.
The given request
is sent and the caller waits until a reply arrives or
a timeout occurs. The event manager will call handle_call/2
to handle
the request.
The return value reply
is defined in the return value of handle_call/2
.
If the specified event handler is not installed, the function returns
{:error, :not_found}
.
Specs
notify(manager, term) :: :ok
Sends an event notification to the event manager
.
The event manager will call handle_event/2
for each
installed event handler.
notify
is asynchronous and will return immediately after the
notification is sent. notify
will not fail even if the specified
event manager does not exist, unless it is specified as an atom.
Removes an event handler from the event manager
.
The event manager will call terminate/2
to terminate the event handler
and return the callback value. If the specified event handler is not
installed, the function returns {:error, :not_found}
.
Starts an event manager process without links (outside of a supervision tree).
See start_link/1
for more information.
Starts an event manager linked to the current process.
This is often used to start the GenEvent
as part of a supervision tree.
It accepts the :name
option which is described under the Name Registration
section in the GenServer
module docs.
If the event manager is successfully created and initialized, the function
returns {:ok, pid}
, where pid is the pid of the server. If there already
exists a process with the specified server name, the function returns
{:error, {:already_started, pid}}
with the pid of that process.
Note that a GenEvent
started with start_link/1
is linked to the
parent process and will exit not only on crashes but also if the parent
process exits with :normal
reason.
Specs
stop(manager) :: :ok
Terminates the event manager
.
Before terminating, the event manager will call terminate(:stop, ...)
for each installed event handler.
Specs
stream(manager, Keyword.t) :: GenEvent.Stream.t
Returns a stream that consumes events from the manager
.
The stream is a GenEvent
struct that implements the Enumerable
protocol. Consumption of events only begins when enumeration starts.
Note streaming is specific to Elixir’s GenEvent and does not work with Erlang ones.
Options
:timeout
- raises if no event arrives in X milliseconds (defaults to:infinity
)
Replaces an old event handler with a new one in the event manager
.
First, the old event handler is deleted by calling terminate/2
with
the given args1
and collects the return value. Then the new event handler
is added and initiated by calling init({args2, state}), where term is the
return value of calling
terminate/2in the old handler. This makes it
possible to transfer information from one handler to another.
The new handler will be added even if the specified old event handler
is not installed or if the handler fails to terminate with a given reason
in which case
state = {:error, term}.
If
init/1in the second handler returns a correct value, this
function returns
:ok`.
Replaces an old event handler with a new monitored one in the event manager
.
Read the docs for add_mon_handler/3
and swap_handler/5
for more information.