2 Gen_Server Behaviour
This chapter should be read in conjunction with
gen_server(3)
, where all interface functions and callback functions are described in detail.2.1 Client-Server Principles
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.
Client-Server Model2.2 Example
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.
2.3 Starting a Gen_Server
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 functiongen_server:start_link/4
. This function spawns and links to a new process, a gen_server.
- The first argument
{local, ch3}
specifies the name. In this case, the gen_server will be locally registered asch3
.
If the name is omitted, the gen_server is not registered. Instead its pid must be used. The name could also be given as{global, Name}
, in which case the gen_server is registered usingglobal:register_name/2
.
- The second argument,
ch3
, is the name of the callback module, that is the module where the callback functions are located.
In this case, the interface functions (start_link
,alloc
andfree
) are located in the same module as the callback functions (init
,handle_call
andhandle_cast
). This is normally good programming practice, to have the code corresponding to one process contained in one module.
- The third argument, [], is a term which is passed as-is to the callback function
init
. Here,init
does not need any indata and ignores the argument.
- The fourth argument, [], is a list of options. See
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}
, whereState
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.2.4 Synchronous Requests - Call
The synchronous request
alloc()
is implemented usinggen_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, andState1
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 channelsChs2
.Thus, the call
ch3:alloc()
will return the allocated channelCh
and the gen_server then waits for new requests, now with an updated list of available channels.2.5 Asynchronous Requests - Cast
The asynchronous request
free(Ch)
is implemented usinggen_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 thusfree
, then returnsok
.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.2.6 Stopping
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 functionterminate(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.2.7 Handling Other Messages
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}.