Erlang Language Using gen_server behavior


Example

A gen_server is a specific finite state machine working like a server. gen_server can handle different type of event:

  • synchronous request with handle_call
  • asynchronous request with handle_cast
  • other message (not defined in OTP specification) with handle_info

Synchronous and asynchronous message are specified in OTP and are simple tagged tuples with any kind of data on it.

A simple gen_server is defined like this:

-module(simple_gen_server).
-behaviour(gen_server).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
         terminate/2, code_change/3]).

start_link() ->
    Return = gen_server:start_link({local, ?MODULE}, ?MODULE, [], []),
    io:format("start_link: ~p~n", [Return]),
    Return.

init([]) ->
    State = [],
    Return = {ok, State},
    io:format("init: ~p~n", [State]),
    Return.

handle_call(_Request, _From, State) ->
    Reply = ok,
    Return = {reply, Reply, State},
    io:format("handle_call: ~p~n", [Return]),
    Return.

handle_cast(_Msg, State) ->
    Return = {noreply, State},
    io:format("handle_cast: ~p~n", [Return]),
    Return.

handle_info(_Info, State) ->
    Return = {noreply, State},
    io:format("handle_info: ~p~n", [Return]),
    Return.

terminate(_Reason, _State) ->
    Return = ok,
    io:format("terminate: ~p~n", [Return]),
    ok.

code_change(_OldVsn, State, _Extra) ->
    Return = {ok, State},
    io:format("code_change: ~p~n", [Return]),
    Return.

This code is simple: every message received is printed to standard output.

gen_server behaviour

To define a gen_server, you need to explicitly declare it in your source code with -behaviour(gen_server). Note, behaviour can be written in US (behavior) or UK (behaviour).

start_link/0

This function is a simple shortcut to call another function: gen_server:start_link/3,4.

start_link/3,4

start_link() ->
    gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).

This function is called when you want to start your server linked to a supervisor or another process. start_link/3,4 can register automatically your process (if you think your process need to be unique) or can simply spawn it like simple process. When called, this function execute init/1.

This function can return these define values:

  • {ok,Pid}
  • ignore
  • {error,Error}

init/1

init([]) ->
    State = [],
    {ok, State}.

init/1 is the first executed function when your server will be launched. This one initialize all prerequisite of your application and return state to newly created process.

This function can return only these defined values:

  • {ok,State}
  • {ok,State,Timeout}
  • {ok,State,hibernate}
  • {stop,Reason}
  • ignore

State variable can be everything, (e.g. list, tuple, proplists, map, record) and remain accessible to all function inside spawned process.

handle_call/3

handle_call(_Request, _From, State) ->
    Reply = ok,
    {reply, Reply, State}.

gen_server:call/2 execute this callback. The first argument is your message (_Request), the second is the origin of the request (_From) and the last one is the current state (State) of your running gen_server behaviour.

If you want a reply to caller, handle_call/3 need to return one of these data structure:

  • {reply,Reply,NewState}
  • {reply,Reply,NewState,Timeout}
  • {reply,Reply,NewState,hibernate}

If you want no reply to caller, handle_call/3 need to return one of these data structure:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

If you want to stop the current execution of your current gen_server, handle_call/3 need to return one of these data structure:

  • {stop,Reason,Reply,NewState}
  • {stop,Reason,NewState}

handle_cast/2

handle_cast(_Msg, State) ->
    {noreply, State}.

gen_server:cast/2 execute this callback. The first argument is your message (_Msg), and the second the current state of your running gen_server behaviour.

By default, this function can't data to the caller, so, you have only two choices, continue current execution:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

Or stop your current gen_server process:

  • {stop,Reason,NewState}

handle_info/2

handle_info(_Info, State) ->
    {noreply, State}.

handle_info/2 is executed when non-standard OTP message come from outside world. This one can't reply and, like handle_cast/2 can do only 2 actions, continuing current execution:

  • {noreply,NewState}
  • {noreply,NewState,Timeout}
  • {noreply,NewState,hibernate}

Or stop the current running gen_server process:

  • {stop,Reason,NewState}

terminate/2

terminate(_Reason, _State) ->
    ok.

terminate/2 is called when an error occur or when you want to shutdown your gen_server process.

code_change/3

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

code_change/3 function is called when you want to upgrade your running code.

This function can return only these defined values:

  • {ok, NewState}
  • {error, Reason}

Starting This process

You can compile your code and start simple_gen_server:

simple_gen_server:start_link().

If you want to send message to your server, you can use these functions:

% will use handle_call as callback and print:
%   handle_call: mymessage
gen_server:call(simple_gen_server, mymessage).

% will use handle_cast as callback and print:
%   handle_cast: mymessage
gen_server:cast(simple_gen_server, mymessage).

% will use handle_info as callback and print:
%   handle_info: mymessage
erlang:send(whereis(simple_gen_server), mymessage).