View Source gen_server Behaviour
It is recommended to read this section alongside gen_server
in STDLIB.
Client-Server Principles
The client-server model is characterized by a central server and an arbitrary number of clients. The client-server model is used for resource management operations, where several different clients want to share a common resource. The server is responsible for managing this resource.
---
title: Client Server Model
---
flowchart LR
client1((Client))
client2((Client))
client3((Client))
server((Server))
client1 --> server
server -.-> client1
client2 --> server
server -.-> client2
client3 --> server
server -.-> client3
subgraph Legend
direction LR
start1[ ] -->|Query| stop1[ ]
style start1 height:0px;
style stop1 height:0px;
start2[ ] -.->|Reply| stop2[ ]
style start2 height:0px;
style stop2 height:0px;
end
Example
An example of a simple server written in plain Erlang is provided in
Overview. The server can be reimplemented using
gen_server
, resulting in this callback module:
-module(ch3).
-behaviour(gen_server).
-export([start_link/0]).
-export([alloc/0, free/1]).
-export([init/1, handle_call/3, handle_cast/2]).
start_link() ->
gen_server:start_link({local, ch3}, ch3, [], []).
alloc() ->
gen_server:call(ch3, alloc).
free(Ch) ->
gen_server:cast(ch3, {free, Ch}).
init(_Args) ->
{ok, channels()}.
handle_call(alloc, _From, Chs) ->
{Ch, Chs2} = alloc(Chs),
{reply, Ch, Chs2}.
handle_cast({free, Ch}, Chs) ->
Chs2 = free(Ch, Chs),
{noreply, Chs2}.
The code is explained in the next sections.
Starting a Gen_Server
In the example in the previous section, gen_server
is started by calling
ch3:start_link()
:
start_link() ->
gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}
start_link/0
calls function gen_server:start_link/4
. This function spawns and
links to a new process, a gen_server
.
The first argument,
{local, ch3}
, specifies the name. The gen_server is then locally registered asch3
.If the name is omitted, the
gen_server
is not registered. Instead its pid must be used. The name can also be given as{global, Name}
, in which case thegen_server
is registered usingglobal:register_name/2
.The second argument,
ch3
, is the name of the callback module, which is the module where the callback functions are located.The interface functions (
start_link/0
,alloc/0
, andfree/1
) are located in the same module as the callback functions (init/1
,handle_call/3
, andhandle_cast/2
). It is usually good programming practice to have the code corresponding to one process contained in a single module.The third argument,
[]
, is a term that is passed as is to the callback functioninit
. Here,init
does not need any indata and ignores the argument.The fourth argument,
[]
, is a list of options. Seegen_server
for the available options.
If name registration succeeds, the new gen_server
process calls the callback
function ch3:init([])
. init
is expected to return {ok, State}
, where
State
is the internal state of the gen_server
. In this case, the state is
the available channels.
init(_Args) ->
{ok, channels()}.
gen_server:start_link/4
is synchronous. It does not return until the
gen_server
has been initialized and is ready to receive requests.
gen_server:start_link/4
must be used if the gen_server
is part of
a supervision tree, meaning that it was started by a supervisor. There
is another function, gen_server:start/4
, to start a standalone
gen_server
that is not part of a supervision tree.
Synchronous Requests - Call
The synchronous request alloc()
is implemented using gen_server:call/2
:
alloc() ->
gen_server:call(ch3, alloc).
ch3
is the name of the gen_server
and must agree with the name used to start
it. alloc
is the actual request.
The request is made into a message and sent to the gen_server
. When the
request is received, the gen_server
calls handle_call(Request, From, State)
,
which is expected to return a tuple {reply,Reply,State1}
. Reply
is the reply
that is to be sent back to the client, and State1
is a new value for the state
of the gen_server
.
handle_call(alloc, _From, Chs) ->
{Ch, Chs2} = alloc(Chs),
{reply, Ch, Chs2}.
In this case, the reply is the allocated channel Ch
and the new state is the
set of remaining available channels Chs2
.
Thus, the call ch3:alloc()
returns the allocated channel Ch
and the
gen_server
then waits for new requests, now with an updated list of available
channels.
Asynchronous Requests - Cast
The asynchronous request free(Ch)
is implemented using gen_server:cast/2
:
free(Ch) ->
gen_server:cast(ch3, {free, Ch}).
ch3
is the name of the gen_server
. {free, Ch}
is the actual request.
The request is made into a message and sent to the gen_server
. cast
, and
thus free
, then returns ok
.
When the request is received, the gen_server
calls
handle_cast(Request, State)
, which is expected to return a tuple
{noreply,State1}
. State1
is a new value for the state of the gen_server
.
handle_cast({free, Ch}, Chs) ->
Chs2 = free(Ch, Chs),
{noreply, Chs2}.
In this case, the new state is the updated list of available channels Chs2
.
The gen_server
is now ready for new requests.
Stopping
In a Supervision Tree
If the gen_server
is part of a supervision tree, no stop function is needed.
The gen_server
is automatically terminated by its supervisor. Exactly how this
is done is defined by a shutdown strategy set in the
supervisor.
If it is necessary to clean up before termination, the shutdown strategy must be
a time-out value and the gen_server
must be set to trap exit signals in
function init
. When ordered to shutdown, the gen_server
then calls the
callback function terminate(shutdown, State)
:
init(Args) ->
...,
process_flag(trap_exit, true),
...,
{ok, State}.
...
terminate(shutdown, State) ->
%% Code for cleaning up here
...
ok.
Standalone Gen_Servers
If the gen_server
is not part of a supervision tree, a stop function can be
useful, for example:
...
export([stop/0]).
...
stop() ->
gen_server:cast(ch3, stop).
...
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast({free, Ch}, State) ->
...
...
terminate(normal, State) ->
ok.
The callback function handling the stop
request returns a tuple
{stop,normal,State1}
, where normal
specifies that it is a normal termination
and State1
is a new value for the state of the gen_server
. This causes the
gen_server
to call terminate(normal, State1)
and then it terminates
gracefully.
Handling Other Messages
If the gen_server
is to be able to receive other messages than requests, the
callback function handle_info(Info, State)
must be implemented to handle them.
Examples of other messages are exit messages, if the gen_server
is linked to
other processes than the supervisor and it is trapping exit signals.
handle_info({'EXIT', Pid, Reason}, State) ->
%% Code to handle exits here.
...
{noreply, State1}.
The final function to implement is code_change/3
:
code_change(OldVsn, State, Extra) ->
%% Code to convert state (and more) during code change.
...
{ok, NewState}.