Hi, I’m trying to get my head around how to link/monitor and restart a couple of dependent genservers.
Imagine a simple application which
- monitors a config file on disk.
- when the file changes it is reloaded, parsed and an event message sent to interested listeners (simple producer)
- So we need a FileSystem watcher keeping an eye on a file and sending us a message when it changes (simple consumer)
- We need some state to hold our listeners
- We need some state to hold the parsed file (and a call will return it)
I’m tempted to structure the app as:
defmodule Database.ConfigDB.Worker do
use GenServer
@doc false
def start_link(args) do
{opts, args} = Keyword.split(args, [:name])
GenServer.start_link(__MODULE__, args, opts)
end
@doc false
def init(args) do
dirs = "/some/config/file"
args = Keyword.put(args, :dirs, dirs)
with {:ok, watcher_pid} <- FileSystem.start_link(args),
:ok <- FileSystem.subscribe(watcher_pid) do
{:ok, %{watcher_pid: watcher_pid, subscribers: %{}}}
else
:ignore -> {:stop, "failed to start filewatcher"}
end
end
def handle_call(:subscribe, {pid, _}, state) do
ref = Process.monitor(pid)
state = put_in(state, [:subscribers, ref], pid)
{:reply, :ok, state}
end
def handle_info(
{:file_event, watcher_pid, {file_path, _events}},
%{backend_pid: watcher_pid} = state
) do
# reload, alert subscribers
# ...
end
So above I start the FileSystem watcher with start_link, so I think this means if it crashes then it’s going to also take out the ConfigDB.Watcher? This is undesirable because I’ve chosen to store some state in the genserver (only subscribers in the example, but lets pretend there is more). So I don’t want to lose this state, just to restart the file watcher
My first thought was to introduce a DynamicSupervisor, have that start my FileSystem and set that up within my init(args) call above. The tricky part seems to be that I need to call:
FileSystem.subscribe(watcher_pid)
So that seems like I need to monitor my FileSystem, notice if it goes down and then keep calling subscribe until it comes back up again? This seems like a problematic solution?
So my next thought is to return to the original setup, so things are linked:
Supervisor -> ConfigDB.Worker -> FileSystem
Then I need to move my state somewhere else. Since right now state is only a list of “send an alert to subscribers” there might even be a well worn path to handle this?
This seems like it should be a simple problem to solve and most of the options above seem subtle and tricky to get right. So what’s the generic (and simple) solution for:
- Some simple producer genserver (could be tailing a file, reading a serial port, spooling tweets, etc)
- Consumer needs to run some kind of simple setup when the producer starts, eg subscribe to it
- Desired behaviour is if the producer falls over it should be a non critical event, just keep trying to restart it and run the setup (subscribe) so that we get events again.
- It’s assumed that failures are rare and not persistent, eg file watcher segfaults, not worried about handling reading Twitter which has gone away for a few hours.
Thanks for advice