inaka

Latest blog entries

/
The Art of Writing a Blogpost

The Art of Writing a Blogpost

Mar 09 2017 : Matias Vera

/
SpellingCI: No more spelling mistakes in your markdown flies!

Feb 14 2017 : Felipe Ripoll

/
Fast reverse geocoding with offline-geocoder

Do you need a blazing fast reverse geocoder? Enter offline-geocoder!

Jan 18 2017 : Roberto Romero

/
Using Jayme to connect to the new MongooseIM REST services

MongooseIM has RESTful services!! Here I show how you can use them in an iOS application.

Dec 13 2016 : Sergio Abraham

/
20 Questions, or Maybe a Few More

20 Questions, or Maybe a Few More

Nov 16 2016 : Stephanie Goldner

/
The Power of Meeting People

Because conferences and meetups are not just about the technical stuff.

Nov 01 2016 : Pablo Villar

/
Finding the right partner for your app build

Sharing some light on how it is to partner with us.

Oct 27 2016 : Inaka

/
Just Play my Sound

How to easily play a sound in Android

Oct 25 2016 : Giaquinta Emiliano

/
Opening our Guidelines to the World

We're publishing our work guidelines for the world to see.

Oct 13 2016 : Brujo Benavides

/
Using NIFs: the easy way

Using niffy to simplify working with NIFs on Erlang

Oct 05 2016 : Hernan Rivas Acosta

/
Function Naming In Swift 3

How to write clear function signatures, yet expressive, while following Swift 3 API design guidelines.

Sep 16 2016 : Pablo Villar

/
Jenkins automated tests for Rails

How to automatically trigger rails tests with a Jenkins job

Sep 14 2016 : Demian Sciessere

/
Erlang REST Server Stack

A description of our usual stack for building REST servers in Erlang

Sep 06 2016 : Brujo Benavides

/
Replacing JSON when talking to Erlang

Using Erlang's External Term Format

Aug 17 2016 : Hernan Rivas Acosta

/
Gadget + Lewis = Android Lint CI

Integrating our Android linter with Github's pull requests

Aug 04 2016 : Fernando Ramirez and Euen Lopez

/
Passwordless login with phoenix

Introducing how to implement passwordless login with phoenix framework

Jul 27 2016 : Thiago Borges

/
Beam Olympics

Our newest game to test your Beam Skills

Jul 14 2016 : Brujo Benavides

/
Otec

Three Open Source Projects, one App

Jun 28 2016 : Andrés Gerace

/
CredoCI

Running credo checks for elixir code on your github pull requests

Jun 16 2016 : Alejandro Mataloni

/
Thoughts on rebar3

Thoughts on rebar3

Jun 08 2016 : Hernán Rivas Acosta

/
See all Inaka's blog posts >>

/
Erlang Event-Driven Applications

Marcelo Gornstein wrote this on January 21, 2013 under dev, erlang .

Introduction

Events are one of the best tools to have in your personal toolbox. There's even a special chapter in the OTP design principles about events, and that's because having an event-driven architecture can do marvels for your code (and your architecture):

  • It's easier for you to really decouple code.
  • It opens the door to interesting new architectural patterns, like CQRS, event sourcing, and event collaboration.
  • The application can be extended by creating "autonomous components" that just "plug into" the system and do their thing, either triggering commands or events. Example events: user_logged_in, user_created, user_voted, incoming_tweet, user_tweeted, etc.
  • Events and commands can be handled by very small event handlers.
  • If you really go for it, it's possible to dispatch events and commands through an mq like rabbitmq, 0mq, etc. This also opens a couple of interesting bonuses:
  • The system can have components done in whatever language you like (Erlang, Scala, Python, Ruby, js -node-, etc.)
  • For scalability, just plug in as many command handlers or event handlers (of the same or different type) as needed.
  • Implementation and demo of "crazy features" are very probable and can be done quickly! They will probably be in languages other than erlang (if using an mq).

Erlang already offers a way to implement event driven architectures: the gen_event behavior. gen_event is one of the default behaviors shipped with Erlang/OTP. For me, it's also one of the coolest features of Erlang/OTP, because you don't get this out-of-the-box (and for free) in many languages or environments, so it's a great feature.

How It Works

Gen_event is all about having one or more of event managers, event handlers, and of course, to dispatch and handle events:

  • Event Manager: An event manager is a gen_event process.
  • Event Handler: The event handlers are the callback modules that actually handle the events, and are registered to one or more event managers.
  • Events: An event is any erlang term, like a tuple, an atom, a list, etc.

In some ways, gen_event is used like the EventEmitter class in nodejs, and also (conceptually speaking) to the observer pattern. The idea is to have some kind of an event bus (the event manager), in charge of receiving the events from the system and route them to the corresponding listeners. The basic workflow is as follows:

  • Create one or more callback modules, implementing the gen_event behavior. There's a complete example at the end of this article, in Appendix B.
  • Start an event mananger process.
  • Register one or more event handlers to it.
  • Dispatch events through the event manager
  • (A)synchronously handle the dispatched events in your event handlers.

Now let's go through each one of those steps in order.

Starting an Event Manager

This is as easy as calling:

{ok, Pid} = gen_event:start().

Or if you plan to include your event manager in a supervisor tree:

{ok, Pid} = gen_event:start_link().

Registering an Event Manager with a Given Name

{ok, Pid} = gen_event:start(my_event_bus).

And also:

{ok, Pid} = gen_event:start_link(my_event_bus).

These functions serve the same purpose as start/0 and start_link/0, but they allow you to register the new event manager with a specific name. To quote the manual:

If EventMgrName={local,Name}, the event manager is registered locally as
Name using register/2.

If EventMgrName={global,GlobalName}, the event manager is registered
globally as GlobalName using global:register_name/2.

If no name is provided, the event manager is not registered.

If EventMgrName={via,Module,ViaName}, the event manager will register with
the registry represented by Module. The Module callback should export the
functions register_name/2, unregister_name/1, whereis_name/1 and send/2,
which should behave like the corresponding functions in global. Thus,
{via,global,GlobalName} is a valid reference.

Registering Event Handlers

Once you have your event manager up and running, it's time to add some event handlers to it by using add_handler/3:

add_handler(my_event_bus, my_event_handler, [arg1, arg2]).

This will add a new event handler to the given event manager (referenced by whatever you pass in as the first argument). The event manager reference can be:

  • The pid.
  • Name, if the event manager is locally registered.
  • {Name, Node}, if the event manager is locally registered at another node, or {global, GlobalName}, if the event manager is globally registered.
  • {via, Module, ViaName}, if the event manager is registered through an alternative process registry.

The second argument specifies the event handler module name, but it can also be {Module, Id}, where Module is the name of the callback module, and Id is useful to identify a specific handler when multiple handlers use the same callback module.

Afterwards, the init/1 callback function is invoked on my_event_handler, passing as the argument whatever term is on the third argument of add_handler/3. init/1 can return:

If successful, the function should return {ok,State} or {ok,State,hibernate}
where State is the initial internal state of the event handler.

If {ok, State, hibernate} is returned, the event manager will go into
hibernation (by calling proc_lib:hibernate/3), waiting for the next event
to occur.

Supervised Event Handlers

You can establish some kind of monitoring (or supervision) between the calling process (the one that's registering the new event handler) and the event handler itself, by using add_sup_handler/3:

add_sup_handler(my_event_bus, my_event_handler, [arg1, arg2]).

As the manual states:

If the calling process later terminates with Reason, the event manager will
delete the event handler by calling Module:terminate/2 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 to
    delete_handler/3, or remove_handler has been returned by a callback
    function (see below).

    shutdown, if the event handler has been removed because the event
    manager is terminating.

    {swapped,NewHandler,Pid}, if the process Pid has replaced the event
    handler with another event handler NewHandler using a call to
    swap_handler/3 or swap_sup_handler/3. a term, if the event handler is
    removed due to an error. Which term depends on the error.

Drawbacks

Event handlers are executed sequentially, so try to keep the code small. If you need to have thousands of event handlers, it might be better to implement some kind of repeater. The idea behind a repeater is to have several sub gen_event's subscribed to the main one. This will distribute the load of notifying all the interested listeners.

On the other hand, if you need to do expensive operations in an event handler, you could try using a gen_event caster, which will listen for events and dispatch individual (regular) erlang messages.

Also, note that gen_event will send messages to all the handlers when a supervised handler exits. So be ready to deal with these messages.

Dispatching Events

Dispatching an event through the event manager is actually pretty easy. Just call gen_event:notify/2:

gen_event:notify(my_event_handler, {new_user_created, User}).

You can read the actual details of how gen_event dispatches the event at the end of the article, in Appendix A.

This will dispatch the event asynchronously. This means that the call will not block and will return immediately. There's a way to dispatch it synchronously, by using gen_event:sync_notify/2:

gen_event:sync_notify(my_event_handler, {new_user_created, User}).

Synchronously here means that the call will block and return only after all event handlers have been called and handled the event.

Using Messages Instead of Notify/2

Another way to invoke the event handlers is by dispatching regular messages to the event manager:

Pid ! {new_user_created}

This will invoke the handle_info/2 on all registered event handlers, according to the gen_event manual:

This function is called for each installed event handler when an event
manager receives any other message than an event or a synchronous request
(or a system message).

Handling Events

To handle the events dispatched with notify/2, your callback module needs to implement the handle_event/2 function. For example:

handle_event(bad_smell, State) ->
  io:format("That's an aweful smell..  go clean your kitty's litter~n~n"),
  {ok, State};

On the other hand, to handle messages, implement the handle_info/2 function:

handle_info(Info, State) ->
  io:format("Got message: ~p", [Info]),
  {ok, State}.

In both cases, State holds the term() returned by init/1.

These two can return the following (quoting the manual, of course):

If the function returns {ok,NewState} or {ok,NewState,hibernate} the event
handler will remain in the event manager with the possible updated internal
state NewState.

If {ok,NewState,hibernate} is returned, the event manager will also go into
hibernation (by calling proc_lib:hibernate/3), waiting for the next event
to occur. It is sufficient that one of the event handlers return
{ok,NewState,hibernate} for the whole event manager process to hibernate.

If the function returns {swap_handler,Args1,NewState,Handler2,Args2} the
event handler will be replaced by Handler2 by first calling
Module:terminate(Args1,NewState) and then Module2:init({Args2,Term}) where
Term is the return value of Module:terminate/2. See gen_event:swap_handler/3
 for more information.

If the function returns remove_handler the event handler will be deleted by
calling Module:terminate(remove_handler,State).

Extra Stuff

Stopping the event manager

Stopping the event manager is sometimes necessary and easily accomplished by calling stop/1:

gen_event:stop(my_event_bus).

This will also call terminate/2 on all the registered event handlers.

Calling functions in an event handler

You can actually call a specific function in an event handler, like you would do with a gen_server, by calling call/3:

ok = gen_event:call(my_event_bus, my_event_handler, {do_something}).

Again, as with a gen_server, you can specify a timeout value for the call by using call/4:

ok = gen_event:call(my_event_bus, my_event_handler, {do_something}, 5000).

gen_event will call the handle_call/2 callback function in the event handler module. For example:

handle_call(_Request, State) ->
  {ok, this_is_my_reply, State}.

Deleting handlers

You can remove your handlers by calling delete_handler/3:

gen_event:delete_handler(my_event_bus, my_event_handler, [arg1, arg2]).

This will remove the handler from the event manager, but also calls terminate/2 passing as arguments the term passed as the third argument, and will return the result of that call.

Listing all registered handlers

To get a list of all currently registered handlers in an event manager, call which_handlers/1:

gen_event:which_handlers(my_event_bus).

Replacing (or swapping) event handlers

It's also possible to change event handlers at runtime, by calling swap_handler/3:

gen_event:swap_handler(my_event_bus, {my_event_handler, [arg1]}, {my_new_event_handler, [arg2]}).

What this does is to first call my_event_handler:terminate([arg1], State) and then my_new_event_handler:init([arg2]), replacing the old event handler with a new one.

If the old handler was supervised, then the new handler will be too. You can of course force this by calling swap_sup_handler/3, with the same arguments.

Thanks

Once again, I'd like to thanks to Fernando "El Brujo" Benavides for his general thoughts on the article, remarks about the drawbacks that gen_event has, and for sharing his work on the gen_event repeater and caster.

Author

Marcelo Gornstein marcelo@inakanetworks.com

Github: marcelog

Homepage: http://marcelog.github.com

Appendix A: How Gen_event Actually Dispatches an Event

The notify/2 call actually ends up in line 504 of the lib/stdlib/src/gen_event.erl file inside your erlang/otp source tree:

case catch Mod1:Func(Event, State) of

Func is the atom handle_event or handle_info. So if your event handler crashes or fails somehow, the event manager won't crash.

And, if running a supervised event handler (one started with add_sup_handler/3), the terminate/2 function is called in line 635 of the same file:

Res = (catch Mod:terminate(Args, State)),

Also for supervised event handlers, a message is sent to the process that registered it in line 648:

case Handler#handler.supervised of
  false ->
      ok;
  Pid ->
      Pid ! {gen_event_EXIT,handler(Handler),Reason},
      ok
  end.

Appendix B: Sample Callback Module Example

-module(my_event_handler).

-behaviour(gen_event).

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

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

handle_info(_Info, State) ->
  {ok, State}.

handle_call(_Request, State) ->
  {ok, not_implemented, State}.

handle_event(bad_smell, State) ->
  io:format("That's an aweful smell..  go clean your kitty's litter~n~n"),
  {ok, State};

handle_event(_Event, State) ->
  {ok, State}.

code_change(_OldVsn, State, _Extra) ->
  {ok, State}.

terminate(_Arg, _State) ->
  ok.