Step 1 of 5 · Reading · ~2 min
Learn
OTP Behaviours
gen_server is OTP's standard behaviour for stateful server processes. Implements the receive-loop pattern with hooks for sync call, async cast, and lifecycle.
-module(counter).
-behaviour(gen_server).
-export([start_link/0, increment/0, value/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% Client API
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, 0, []).
increment() -> gen_server:cast(?MODULE, increment).
value() -> gen_server:call(?MODULE, get).
%% Server callbacks
init(Initial) -> {ok, Initial}.
handle_call(get, _From, State) -> {reply, State, State}.
handle_cast(increment, State) -> {noreply, State + 1}.
handle_info(_Msg, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
Two-part split:
- Client API — what callers use (
start_link,increment,value) - Server callbacks — internal protocol (
init,handle_call, ...)
This is THE Erlang convention. Hides the gen_server protocol behind a clean API.
Return shapes for callbacks:
%% handle_call:
{reply, Reply, NewState}
{reply, Reply, NewState, Timeout}
{reply, Reply, NewState, hibernate}
{noreply, NewState} %% no reply yet — manual reply later
{stop, Reason, Reply, NewState}
%% handle_cast:
{noreply, NewState}
{stop, Reason, NewState}
call vs cast:
call— synchronous, caller waits for reply. Use for queries.cast— asynchronous, returnsokimmediately. Use for fire-and-forget actions.
Why gen_server over hand-rolled receive loops:
- Tested protocol — handles caller crashes, monitors, timeouts
- Standard message format — tools (observer, recon) can introspect
- Plays nicely with supervisors
- Hot code reload via
code_change
Running this on our grader — the exercises here run as a single escript-style
main module, so a full -behaviour(gen_server) module is not what you submit. Two
things follow. First, the OTP behaviour above is still exactly what you write in a real
project. Second, when you hand-roll the same idea in a one-shot script, spawn a fun,
not a module-function-arity triple:
%% Fails here with {undef, {main, loop, ...}} — the module is not
%% loaded under its name in this environment:
Pid = spawn(?MODULE, loop, [0]),
%% Works — the fun closes over loop/1 directly:
Pid = spawn(fun() -> loop(0) end),
That distinction is not gen_server trivia. It is the difference between a script that
runs and one that hangs: if the spawned process dies immediately with undef, your
receive waits for a reply that can never arrive and the run is killed on the time
limit.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…