A GenServer is a process that holds state and
handles requests one at a time. You write the
callbacks; OTP provides the receive loop, timeouts,
error reporting and supervision hooks.
1
defmoduleCounterdouseGenServer# The client API: plain functions other processes# call. They hide the message format.defstart_link(initial)doGenServer.start_link(__MODULE__,initial,name:__MODULE__)enddefincrement(by\\1),do:GenServer.cast(__MODULE__,{:increment,by})defvalue,do:GenServer.call(__MODULE__,:value)# Server callbacks run inside the GenServer process.@impltruedefinit(initial)whenis_integer(initial),do:{:ok,initial}@impltruedefhandle_call(:value,_from,count),do:{:reply,count,count}@impltruedefhandle_cast({:increment,by},count),do:{:noreply,count+by}end
call is synchronous: it waits for a reply (5s
timeout by default). cast is fire-and-forget.
Messages are processed in order, so the casts above
are applied before the call is answered. That
ordering is per sender, and it's the reason a
GenServer never needs locks.
3
IO.inspect(Process.alive?(pid),label:"alive?")
alive?: true
Plain messages (not calls or casts), like timers or
monitors, arrive in handle_info/2.
Pitfall: a GenServer is a bottleneck by
design: one process, one message at a time. Doing
slow work (HTTP calls, heavy computation) inside
handle_call blocks every other caller. Hand slow
work to a Task, or read hot data from ETS instead.
Pitfall:GenServer.call/3 from inside the same
GenServer's callback deadlocks until the timeout,
because the process is busy waiting on itself.