GenServer
GenServer (generic server) is the core OTP behaviour for stateful processes.
32.1 A complete GenServer
defmodule Stack do
use GenServer
# ---- Client API ----
def start_link(initial \\ []), do: GenServer.start_link(__MODULE__, initial, name: __MODULE__)
def push(item), do: GenServer.cast(__MODULE__, {:push, item})
def pop, do: GenServer.call(__MODULE__, :pop)
def peek, do: GenServer.call(__MODULE__, :peek)
# ---- Server callbacks ----
@impl true
def init(initial), do: {:ok, initial}
@impl true
def handle_call(:pop, _from, [h | t]), do: {:reply, {:ok, h}, t}
def handle_call(:pop, _from, []), do: {:reply, {:error, :empty}, []}
def handle_call(:peek, _from, state), do: {:reply, List.first(state), state}
@impl true
def handle_cast({:push, item}, state), do: {:noreply, [item | state]}
@impl true
def handle_info(msg, state) do
IO.inspect(msg, label: "unexpected message")
{:noreply, state}
end
@impl true
def terminate(reason, _state), do: IO.puts("Terminating: #{inspect(reason)}")
end32.2 Callbacks and return values
{:ok, state}, {:ok, state, {:continue, term}}, :ignore, {:stop, reason} GenServer.call (sync)
{:reply, reply, state}, {:noreply, state}, {:stop, reason, reply, state} GenServer.cast (async)
{:noreply, state}, {:stop, reason, state} any other message (timers, monitors, raw send) {:noreply, state} {:continue, term} stop (only if trapping exits or stopped explicitly) {:ok, new_state} status/crash reports
Extras in returns: a timeout ({:reply, r, s, 5000} → :timeout message), :hibernate, {:continue, arg}.
32.3 Deferred replies
def handle_call(:slow, from, state) do
Task.start(fn -> GenServer.reply(from, compute()) end)
{:noreply, state}
end32.4 Periodic work
def init(state) do
schedule()
{:ok, state}
end
def handle_info(:work, state) do
do_work()
schedule()
{:noreply, state}
end
defp schedule, do: Process.send_after(self(), :work, :timer.minutes(1))32.5 Expensive initialization with handle_continue
def init(args), do: {:ok, %{data: nil}, {:continue, :load}}
def handle_continue(:load, state), do: {:noreply, %{state | data: load_from_db()}}32.6 Naming
GenServer.start_link(M, arg, name: :atom_name)
GenServer.start_link(M, arg, name: {:global, :name})
GenServer.start_link(M, arg, name: {:via, Registry, {MyRegistry, "key"}})32.7 GenServer pitfalls
A GenServer processes one message at a time — it can become a bottleneck.
Don't use a GenServer just to organize code; use modules/functions. Use processes for *runtime* concerns: state, concurrency, isolation.
call from a GenServer to itself deadlocks.
Keep handle_* fast; offload heavy work to Tasks.
Calls have a default 5 s timeout.