Packages
auto_process_registry
0.1.0
A simple process registry that can instantiate new processes on the fly
Current section
Files
Jump to
Current section
Files
lib/auto_process_registry.ex
defmodule AutoProcessRegistry do
@moduledoc """
AutoProcessRegistry is a process registry that is setup with a
starter function to start new processes on demand.
Processes aren't supervised. The idea is that if they die, for
whichever reason, they can be restarted on demand when necessary. It
is not hard, though, to make the starter function register a process
under a supervisor.
The registry implements the `whereis` message that is needed to
make it usable for via naming lookups in the style of
`{:via, YourRegistryModule, key}`.
Here is a quick example using agents, lifted from the test suite:
defmodule SampleModule do
use AutoProcessRegistry
def start_new(config, key) do
Agent.start_link(fn -> config * key end)
end
end
SampleModule.start_link(10)
With this module defined, it's now possible to launch processes (in
this case, simple agents, but it should be hard to see how to extend
this for `GenServer`, etcetera:
Agent.get({:via, SampleModule, 42}, fn(key) -> key end) # --> 420
Agent.get({:via, SampleModule, 666}, fn(key) -> key end) # --> 6660
A potential use case for this library is for example genservers that have
their state persisted in a database. `start_new` can be setup to read
data for a primary key, start a process with the state from the database,
and from then reading from the database basically is just done on the fly
by starting to talk to an instance with a certain primary key. What would
be neat in that case, of course, is to extend this with an LRU cache and a
maximum number of live processes, maybe for a later iteration.
"""
@doc """
Start the GenServer process implementing the registry. The GenServer
is registered with the module name, so keeping the pid around is not
necessary. The config argument is passed on to `start_new`
"""
@callback start_link(any) :: GenServer.on_start
@doc """
Start a new process with the config argument as the first argument
and the key as the second argument.
"""
@callback start_new(any, any) :: GenServer.on_start
@doc false
defmacro __using__(_) do
quote do
@behaviour AutoProcessRegistry
use GenServer
# Client API
@doc false
def start_link(config) do
GenServer.start_link(__MODULE__, config, name: __MODULE__)
end
def send(key, msg) do
case whereis_name(key) do
pid when is_pid(pid) ->
Kernel.send(pid, msg)
:undefined ->
{:badarg, {key, msg}}
end
end
def whereis_name(key) do
GenServer.call(__MODULE__, {:whereis_name, key})
end
# Server API #
def init(config) do
{:ok, {config, %{}}}
end
def handle_call({:whereis_name, key}, _from, {config, registry}) do
case Map.get(registry, key, nil) do
nil ->
{:ok, pid} = start_new(config, key)
Process.monitor(pid)
registry = Map.put(registry, key, pid)
{:reply, pid, {config, registry}}
pid ->
{:reply, pid, {config, registry}}
end
end
def handle_info({:DOWN, _ref, :process, pid, _reason}, {config, registry}) do
{:noreply, {config, deregister(registry, pid)}}
end
def handle_info(_info, state), do: {:noreply, state}
# Helper Functions #
defp deregister(registry, pid) when is_pid(pid) do
case Enum.find(registry, nil, fn({_key, cur_pid}) -> cur_pid == pid end) do
nil -> registry
{key, _pid} -> deregister(registry, key)
end
end
defp deregister(registry, key) do
Map.delete(registry, key)
end
end
end
end