UPDATE 2018-03-30: GenEvent is deprecated and should no longer be used!

Lately I was playing around with GenEvent in Elixir. To getting into the concept of it, I built a simple Logger, that would listen for events and log the message from the event to the console:

defmodule LoggerHandler do
  use GenEvent
  require Logger

  def handle_event({:log, x}, messages) do
    {:ok, [x|messages]}

I opened iex and tested my code:

iex session

iex -S mix
{:ok, pid} = GenEvent.start_link([])
GenEvent.add_mon_handler(pid, LoggerHandler, [])
GenEvent.notify(pid, {:log, "It works!"})
GenEvent.notify(pid, %{log: "That won't work!"})

As you can see, the %{log: "That won't work!"} crashes the LoggerHandler as there is no code to handle this event; pattern matching for {:log, x} fails. The handler is then down when I call GenEvent.notify(pid, {:log, "It works!"}) again.

So how do we ensure that the GenEvent gets restarted when an error occurs? I searched some time for it and had a look at the Logger.Watcher from the Elixir repo which does basically the same. I also found a very basic solution in Rodney Norris’ blog post “Event handling in Elixir”.

To sum it all up: a GenServer can be supervised, so it is the best solution to ensure that an Event handler is up and running even if it fails to handle an event. This is a simple example how one could implement it:

defmodule LoggerHandlerWatcher do
  use GenServer

  @doc """
    starts the GenServer, this should be done by a Supervisor to ensure
    restarts if it itself goes down
  def start_link(logger_pid) do
    GenServer.start_link(__MODULE__, logger_pid)

  @doc """
    inits the GenServer by starting a new handler
  def init(logger_pid) do

  @doc """
    handles EXIT messages from the GenEvent handler and restarts it
  def handle_info({:gen_event_EXIT, _handler, _reason}, logger_pid) do
    {:ok, logger_pid} = start_handler(logger_pid)
    {:noreply, logger_pid}

  @doc """
    starts a new handler listening for events on `logger_pid`
  defp start_handler(logger_pid) do
    case GenEvent.add_mon_handler(logger_pid, LoggerHandler, []) do
     :ok ->
       {:ok, logger_pid}
     {:error, reason}  ->
       {:stop, reason}

Now to sum things up, this is what happens with the restartable GenEvent LoggerHandler:

iex -S mix
{:ok, logger_pid} = GenEvent.start_link([])
{:ok, _} = LoggerHandlerWatcher.start_link(logger_pid)
GenEvent.notify(logger_pid, {:log,  "message"})
GenEvent.notify(logger_pid, %{log: "message"}) # this will fail again
GenEvent.notify(logger_pid, {:log,  "message"}) # this will log again into the restarted handler

The implementation shown above will need some more fine-tuning and testing (what if the handler fails to start?), but I hope I made my point clear: GenEvent is a great implementation for everything that behaves like real events but supervising it is important and can be done with the help of a GenServer implementation that watches the GenEvent.