[Erlang Systems]

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.

clientserver
Client-Server Model

2.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 function gen_server:start_link/4. This function spawns and links to a new process, a gen_server.

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.

2.4 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 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.

2.5 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 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 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.
    

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}.
    

Copyright © 1991-2003 Ericsson Utvecklings AB