We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Name dynamic processes with `:via` tuples
almirsarajcic
The moment you need one process per game, per room, or per upload, the naming problem shows up — and the tempting fix is String.to_atom("game_#{id}"). That builds an atom from user input, which is never garbage collected and will eventually take down the VM. It also leaves the name registered against nothing when the process dies. Registry solves both at once.
defmodule MyApp.Game do
use GenServer
def start_link(id), do: GenServer.start_link(__MODULE__, id, name: via(id))
def score(id), do: GenServer.call(via(id), :score)
defp via(id), do: {:via, Registry, {MyApp.GameRegistry, {:game, id}}}
@impl GenServer
def init(id), do: {:ok, %{id: id, score: 0}}
@impl GenServer
def handle_call(:score, _from, state), do: {:reply, state.score, state}
end
A :via tuple is accepted anywhere a process name is — GenServer.start_link/3, GenServer.call/3, GenServer.cast/2, DynamicSupervisor.start_child/2. The key can be any term, so {:game, id} works directly with no string interpolation and no atom creation.
Start the registry in your supervision tree:
children = [
{Registry, keys: :unique, name: MyApp.GameRegistry},
{DynamicSupervisor, name: MyApp.GameSupervisor, strategy: :one_for_one}
]
Deregistration is automatic
This is the part that makes it worth the indirection. Registry monitors every registered process and removes the entry when it exits — no cleanup callback, no stale name:
{:ok, pid} = MyApp.Game.start_link("abc")
Registry.lookup(MyApp.GameRegistry, {:game, "abc"})
# => [{#PID<0.123.0>, nil}]
GenServer.stop(pid)
Registry.lookup(MyApp.GameRegistry, {:game, "abc"})
# => []
With atom names you would have to unregister by hand, and a crash would skip that code entirely.
Start-or-find
Because registration happens inside start_link/3, a duplicate start fails cleanly with the existing pid, which is exactly what you need for lazy process creation:
def ensure_started(id) do
case DynamicSupervisor.start_child(MyApp.GameSupervisor, {MyApp.Game, id}) do
{:ok, pid} -> {:ok, pid}
{:error, {:already_started, pid}} -> {:ok, pid}
error -> error
end
end
No lookup-then-start race — the registry decides the winner atomically.
Values, not just names
The third element of the registration key can carry metadata, and keys: :duplicate turns the same registry into a pubsub-style index:
Registry.register(MyApp.GameRegistry, {:lobby, lobby_id}, %{role: :player})
Registry.dispatch(MyApp.GameRegistry, {:lobby, lobby_id}, fn entries ->
for {pid, %{role: :player}} <- entries, do: send(pid, :starting)
end)
The rule of thumb: atoms are for names you write in source code, Registry is for names you compute at runtime. Anything derived from an id, a slug, or user input belongs in the second category.
copied to clipboard