पाठ 11 / 25
GenServer: Stateful Servers
Implement a GenServer with a client API and server callbacks.
The standard server behaviour
Writing receive loops by hand is error-prone, so OTP provides GenServer, a behaviour for a process that holds state and handles requests. You implement callbacks: init/1 sets the initial state; handle_call/3 handles synchronous requests from GenServer.call, replying with {:reply, reply, new_state}; handle_cast/2 handles asynchronous GenServer.cast messages with {:noreply, new_state}; and handle_info/2 handles any other messages, such as timers from Process.send_after or monitor notifications. By convention, a module exposes a client API (Cart.add_item(pid, sku)) that wraps call and cast, keeping the message format private. GenServer.call has a default timeout of 5 seconds. A GenServer processes one message at a time, which serialises access to its state, but also means a slow callback blocks everyone; move heavy work into Tasks. Use GenServers to model stateful resources: caches, rate limiters, connection managers, game rooms or a shopping cart per user.
A shopping cart GenServer
Client API on top, callbacks below.
defmodule Shop.Cart do
use GenServer
# ---- client API ----
def start_link(customer_id),
do: GenServer.start_link(__MODULE__, customer_id, name: via(customer_id))
def add_item(customer_id, sku, qty), do: GenServer.cast(via(customer_id), {:add, sku, qty})
def items(customer_id), do: GenServer.call(via(customer_id), :items)
defp via(customer_id), do: {:via, Registry, {Shop.CartRegistry, customer_id}}
# ---- server callbacks ----
@impl true
def init(customer_id) do
Process.send_after(self(), :expire, :timer.minutes(30))
{:ok, %{customer_id: customer_id, items: %{}}}
end
@impl true
def handle_cast({:add, sku, qty}, state) do
items = Map.update(state.items, sku, qty, &(&1 + qty))
{:noreply, %{state | items: items}}
end
@impl true
def handle_call(:items, _from, state), do: {:reply, state.items, state}
@impl true
def handle_info(:expire, state), do: {:stop, :normal, state} # abandoned cart
end
# usage (with Registry started under the supervisor, see next topic):
# {:ok, _} = Shop.Cart.start_link("c-1")
# Shop.Cart.add_item("c-1", "pen", 2)
# Shop.Cart.items("c-1") # %{"pen" => 2}A shop counter with one clerk
A GenServer is a counter with one clerk who keeps the ledger. Customers can wait for an answer (call) or drop a note in the box (cast). The clerk serves one customer at a time, so the ledger never gets mixed up, but a slow customer holds up the queue.
त्वरित जाँच: Which GenServer callback handles synchronous requests made with GenServer.call?
- handle_cast/2
- handle_call/3
- handle_info/2
- init/1
Answer
handle_call/3 — handle_call replies to the caller, which waits up to a timeout.