Skip to content
Lesson 1 of 7

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, returns ok immediately. 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.

Up nextSupervision TreesOTP Behaviours

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…