Gen server
From Telecomix Crypto Munitions Bureau
gen_server is used to create worker processes that
- (optional) Communicates with a group of client processes
- (optional) Maintains a state
- Performs computations
- Replies to messages
[edit] How it works
There is one module in the OTP that is named gen_server that simplifies the creation and handling of servers.
Your code implements the gen_server "behaviour" (one form of worker pattern).
Others can then easily send messages to your server using the gen_server API.
[edit] API mapping between gen_server and your code
GEN_SERVER YOUR CODE MOAR?? start_link() init() starts & attach your server to a supervisor start() init() starts your server in stand-alone mode call() handle_call() send/handle synchronous message multi_call() handle_call() send message to multiple servers at once cast() handle_cast() send/handle asynchronous message abcast() handle_cast() send message to multiple servers at once reply() --- used to avoid handle_info() in handle_call() enter_loop() --- turns a non-server to a gen_server-server --- handle_info() informs your code of timeouts / nonstandard messages
Generally, other code will call gen_server:call() or gen_server:cast() when they want to communicate with your server. Thus, handle_call() and handle_cast() is where the shit happens.
[edit] gen_server API
This is the interface that the gen_server-module has.
The API is described in detail at erldocs.org.
- gen_server:start_link/3 - attaches a gen_server to a supervision tree. Optionally registers the name of the gen_server module.
- Will call MODULE:init and wait until it completes before returning
- Returns: {ok,Pid} or ignore or {error,Error}
- Can optionally register a name for the gen_server, so that it can receive messages from multi_call or abcast
- gen_server:start/3-4 - creates a standalone gen_server (not part of supervision tree)
- Works very much like start_link
- gen_server:call/2-3 - sends synchronous message to gen_server module.
- Will call MODULE:handle_call
- Returns: whatever your code returns (can be any erlang term)
- gen_server:multi_call/2-4 - sends synchronous message to a group of gen_servers
- Will call MODULE:handle_call
- Returns: a list of {Node, Reply}. One entry for each reply.
- Can send message to ALL gen_servers registered under a name at ALL nodes in a cluster, or to all gen_servers registered under a name in a specified set of nodes in the cluster
- gen_server:cast/2 - sends asynchronous message to a gen_server
- Will call MODULE:handle_cast
- Returns: ok (the reply might come at a later time, or not at all.)
- gen_server:abcast/2-3 - sends asynchronous messages to a group of gen_servers
- Will call MODULE:handle_cast
- Returns: ok (the replies might come at a later time, or not at all.)
- Can send message to ALL gen_servers registered under a name at ALL nodes in a cluster, or to all gen_servers registered under a name in a specified set of nodes in the cluster
- gen_server:reply/2 - should be used to avoid timeouts, see handle_info()
- used to reply to a call or multi_call if the reply should be sent before MODULE:handle_call is complete.
- Returns: the return value of this function should be ignored.
- gen_server:enter_loop/3-5 - turns a non-gen_server process into a gen_server-process.
- If you know how to use this function, you dont need to look here
[edit] MODULE Interface
This is the interface that you should implement in your code.
Functions not implemented by your code will use the gen_server default, which does nothing. (generates warnings)
Below are just notes that I made for myself, to more quickly remember what i read.
init(State) ->
returns any of
{ok, State}
{ok, State, Timeout} - operations will take less than Timeout ms or else handle_info()
{ok, State, hibernate} - hibernate = operations will take quite some time, optimize for this
{stop, Reason} - crash
ignore - do not use
%%%% gen_server:call() + gen_server:multi_call()
handle_call(Request, From, State) ->
returns any of
{reply,Reply,NewState}
{reply,Reply,NewState,Timeout}
{reply,Reply,NewState,hibernate}
{noreply,NewState} - send no reply. use gen_server:reply() to avoid handle_info()
{noreply,NewState,Timeout}
{noreply,NewState,hibernate}
{stop,Reason,Reply,NewState} - crash
{stop,Reason,NewState} - crash
%%%% gen_server:cast() + gen_server:abcast()
handle_cast(Message, State) ->
returns any of
{noreply,NewState}
{noreply,NewState,Timeout}
{noreply,NewState,hibernate}
{stop,Reason,NewState} - crash
%%%% notifies you that you either failed to reply before Timeout
%%%% OR received a nonstandard message
handle_info(Info, State) ->
returns any of
{noreply,NewState}
{noreply,NewState,Timeout}
{noreply,NewState,hibernate}
{stop,Reason,NewState}
%%%% does not matter what you return from this function
%%%% is only used to notify your code that it is about to shut down
%%%% make sure to save your state somehow
terminate(Reason, State) ->
%%%% notifies your process that it should switch to a new piece of code
code_change(PreviousVersion, State, Extra) ->
%%%% if you know what this one does, you dont need to read this
format_status(Opt, [PDict, State]) ->
[edit] Example
Server that computes whole-integer squares of numbers sent to it. State is set to the last computed Square.
-module(name_of_module).
-behaviour(gen_server).
-export([start_link/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
%% Make life simpler %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
start_link() ->
gen_server:start_link(?MODULE, [], []).
%% gen_server interface for our module %%%%%%%%%%%%%%%%%%
init(_State) ->
{ok, []}.
handle_call(Request, _From, State) ->
if is_integer(Request) ->
NewState = compute_square(Request),
{reply, NewState, NewState};
true ->
{reply, error, State}
end.
handle_cast(_Message, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
State.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% Private functions %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compute_square(Number) ->
Number * Number.

