This chapter should be read in conjunction with
gen_server(3)
, where all interface functions and callback
functions are described in detail.
The client-server model is characterized by a central server and an arbitrary number of clients. The client-server model is generally used for resource management operations, where several different clients want to share a common resource. The server is responsible for managing this resource.
An example of a simple server written in plain Erlang was
given in Overview.
The server can be re-implemented 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.
In the example in the previous section, the gen_server is started
by calling ch3:start_link()
:
start_link() -> gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}
start_link
calls the function
gen_server:start_link/4
. This function spawns and links to
a new process, a gen_server.
{local, ch3}
specifies the name. In
this case, the gen_server will be locally registered as
ch3
.{global, Name}
, in which case the gen_server is
registered using global:register_name/2
.ch3
, is the name of the callback
module, that is the module where the callback functions are
located.start_link
,
alloc
and free
) are located in the same module as
the callback functions (init
, handle_call
and
handle_cast
). This is normally good programming
practice, to have the code corresponding to one process
contained in one module.init
. Here, init
does not
need any indata and ignores the argument.gen_server(3)
for 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()}.
Note that gen_server:start_link
is synchronous. It does
not return until the gen_server has been initialized and is ready
to receive requests.
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 which should 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 remaining available channels Chs2
.
Thus, the call ch3:alloc()
will return the allocated
channel Ch
and the gen_server then waits for new requests,
now with an updated list of available channels.
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 server.
handle_call({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.
If the gen_server is part of a supervision tree, no stop function
is needed. The gen_server will automatically be terminated by its
supervisor calling exit(Pid, shutdown)
.
The gen_server process does not trap exit signals. If it is
necessary to do some cleaning up before termination, it should
be set to trap exit signals in the init
function and
another callback function terminate(Reason, State)
should
be implemented doing the cleaning up:
init(Args) -> ..., process_flag(trap_exit, true), ..., {ok, State}. ... terminate(shutdown, State) -> ..code for cleaning up here.. ok.
If the gen_server is not part of a supervision tree, a stop
function may be useful. The gen_server will automatically call
the callback function terminate(Reason, State)
if another
of the callback functions returns {stop, Reason, State2}
instead of {reply,...}
or {noreply,...}
. 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.
If the gen_server should be able to receive other messages than
requests, the callback function handle_info(Info, State)
must be implemented to handle them. Examples on other messages
could be for example timeouts or exit messages, if the gen_server
is linked to other processes (than the supervisor) and trapping
exit signals.
handle_info({'EXIT', Pid, Reason}, State) -> ..code to handle exits here.. {noreply, State}.