Many users seem to use the gen_server for absolutely everything<br>and often force their problems to fit the gen_sever even though the<br>gen_server is not appropriate for their problems.<br><br>The gen_server is an extremely simple bit of code<br>
which can be easily changed to fit other problems, though people don't<br>often do this.<br><br>In this posting I'll explain the basic idea of how the gen_server works.<br><br>To illustrate this I've written mini_gs.erl - this is a mini gen_server<br>
if you understand mini_gs.erl you'll have understood 98% of how the real <br>gen_server works. The real gen_server just adds a load of "bells and whistles" <br>to mini_gs.erl. <br><br>mini_gs.erl has a compatible interface to gen_server.erl (for a subset of<br>
the gen_server API)<br><br>  -module(mini_gs).<br>  -export([start_link/4, call/2]).<br><br>  %% this module behaves just like the gen-server for a sub-set of the gen_server<br>  %% commands<br><br>  start_link({local,Name}, Mod, Args, _Opts) -><br>
      register(Name, spawn(fun() -> start(Mod, Args) end)).<br><br>  call(Name, X) -><br>      Name ! {self(), Ref = make_ref(), X},<br>      receive<br>       {Ref, Reply} -> Reply<br>      end.<br><br>  start(Mod, Args) -><br>
     {ok, State} = Mod:init(Args),<br>     loop(Mod, State).<br><br>  loop(Mod, State) -><br>     receive<br>     {From, Tag, X} -><br>        case Mod:handle_call(X, From, State) of<br>        {reply, R, State1} -><br>
            From ! {Tag, R},<br>            loop(Mod, State1)<br>        end<br>    end.<br><br>There. That wasn't so painful. The client starts by calling<br><br>   min_gs:start_link({local,Name}, Mod, Args, Opts)<br>
<br>I've ignored Opts in mini_gs, also frozen the name of the server to be of the form<br>{local, Name} (gen_server has more general arguments for the name of the server)<br><br>What happens?<br><br>mini_gs calls Mod:init(Args) to initialize the server, this must return {ok, State}<br>
and State becomes the initial state of the server.<br><br>Now mini_gs calls loop(Mod, State.) When mini_gs receives a message {From, Tag, X}<br>it calls Mod:handle_call(X, From, State) which returns {repy, R, State1}. R1 is<br>
sent back to the client, and the server calls loop/2 with the new state State1.<br><br>That's it. call/2 is an interface routine to abstract out the interface between the<br>client and the sever. <br><br>Now we can write a simple client application.<br>
<br>  -module(kv).<br><br>  %% These define the client API<br>  -export([start/0, store/2,lookup/1]).<br><br>  %% these must be defined because they are called by gs<br>  -export([init/1, handle_call/3]).<br><br>  -define(GS, mini_gs).<br>
  %% -define(GS, gen_server).<br><br>  %% define the client API <br><br>  start()        -> ?GS:start_link({local,someatom}, kv, foo, []).<br>  store(Key,Val) -> ?GS:call(someatom, {putval,Key,Val}).<br>  lookup(Key)    -> ?GS:call(someatom, {getval,Key}).<br>
<br>  %% define the internal routines<br>  init(foo) -> {ok, dict:new()}.<br><br>  handle_call({putval, Key, Val}, _From, Dict) -><br>     {reply, ok, dict:store(Key, Val, Dict)};<br>  handle_call({getval,Key}, _From, Dict) -><br>
    {reply, dict:find(Key, Dict), Dict}.<br> <br><br>This module can call either gen_server or mini_gs (just change the define statement)<br><br>So now we have turned a single process key-value store (using dict) into a global<br>
key-value store.  Note that kv.erl never uses the primitives spawn_link, send, receive<br>or do on. ie kv.erl is written with pure *sequential* code.<br><br>This is *why* we made the gen_server abstraction. You can write well-typed<br>
sequential code (the handle_call and init functions) to parametrize<br>a concurrent behavior, ie you need to know nothing about concurrency to<br>get the job done. We've "abstracted out" the concurrency.<br>
<br>Things become problematic when you do not entirely understand the abstraction.<br>Maybe the abstraction is inappropriate for your needs. I have seen many examples<br>of code where the gen_server *is* inappropriate. The acid test is "does the<br>
gen_sever code look like spaghetti" if the answer is yes then all you have done<br>is shoe horn the applications into an inappropriate form. In this case<br>you should ditch the gen_server and roll-your own.<br><br><br>
/Joe<br>