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.