Last active
December 4, 2020 05:41
-
-
Save jfcloutier/e3324eb5ef9b06d5a111 to your computer and use it in GitHub Desktop.
Fault-tolerant event managers in Elixir
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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