Skip to content

Instantly share code, notes, and snippets.

@jfcloutier
Last active December 4, 2020 05:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jfcloutier/e3324eb5ef9b06d5a111 to your computer and use it in GitHub Desktop.
Save jfcloutier/e3324eb5ef9b06d5a111 to your computer and use it in GitHub Desktop.
Fault-tolerant event managers in Elixir
# By default, GenEvent event handlers fail silently and are not automatically restarted. Not good.
# There's an easy enough way to correct this.
# You'll need to
# 1. define a supervised event manager as a GenServer,
# 2. register its GenEvent handlers with monitoring enabled
# 3. catch handler exits as "out of band" messages (handle_info)
# 4. stop the event manager when any handler crashes, relying on the supervisor to restart the event
# manager who will then restart all of its GenEvent handlers.
# This approach assumes that event handling is either stateless or can reset its state without issues.
defmodule EventManager do
@moduledoc "An event manager assumed to be started as a permanent worker by its supervisor"
use GenServer
require Logger
@name __MODULE__
@dispatcher :event_dispatcher
# API
def start_link() do
GenServer.start_link(@name, [], [:name @name])
end
# Called by whoever generated the event
def notify_of_something(something) do
GenServer.cast(@name, {:notify_of_something, something})
end
# Add other event notification functions here
# CALLBACKS
def init(_) do
GenEvent.start_link(name: @dispatcher)
register_handlers()
{:ok, []}
end
def handle_cast({:notify_of_something, something}, state) do
GenEvent.notify(@dispatcher, {:something, something})
{:noreply, state}
end
# Add other cast handling callbacks here
@doc "Handles the exit message from crashed, monitored GenEvent handlers"
def handle_info({:gen_event_EXIT, crashed_handler, error}, state) do
Logger.error("#{crashed_handler} crashed. Restarting #{@name}.")
{:stop, {:handler_died, error}, state}
end
# Private
defp register_handlers() do
# Notice that we call the add_mon_handler function, not add_handler
GenEvent.add_mon_handler(@dispatcher, SomethingHandler, [])
# Add other monitored handlers here
end
end
# And that's it. The implementations of the supervisor and the GenEvent handlers are as usual.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment