[erlang-questions] The gen server simplified (how it works)

Joe Armstrong erlang@REDACTED
Wed Apr 20 10:05:52 CEST 2011


Many users seem to use the gen_server for absolutely everything
and often force their problems to fit the gen_sever even though the
gen_server is not appropriate for their problems.

The gen_server is an extremely simple bit of code
which can be easily changed to fit other problems, though people don't
often do this.

In this posting I'll explain the basic idea of how the gen_server works.

To illustrate this I've written mini_gs.erl - this is a mini gen_server
if you understand mini_gs.erl you'll have understood 98% of how the real
gen_server works. The real gen_server just adds a load of "bells and
whistles"
to mini_gs.erl.

mini_gs.erl has a compatible interface to gen_server.erl (for a subset of
the gen_server API)

  -module(mini_gs).
  -export([start_link/4, call/2]).

  %% this module behaves just like the gen-server for a sub-set of the
gen_server
  %% commands

  start_link({local,Name}, Mod, Args, _Opts) ->
      register(Name, spawn(fun() -> start(Mod, Args) end)).

  call(Name, X) ->
      Name ! {self(), Ref = make_ref(), X},
      receive
       {Ref, Reply} -> Reply
      end.

  start(Mod, Args) ->
     {ok, State} = Mod:init(Args),
     loop(Mod, State).

  loop(Mod, State) ->
     receive
     {From, Tag, X} ->
        case Mod:handle_call(X, From, State) of
        {reply, R, State1} ->
            From ! {Tag, R},
            loop(Mod, State1)
        end
    end.

There. That wasn't so painful. The client starts by calling

   min_gs:start_link({local,Name}, Mod, Args, Opts)

I've ignored Opts in mini_gs, also frozen the name of the server to be of
the form
{local, Name} (gen_server has more general arguments for the name of the
server)

What happens?

mini_gs calls Mod:init(Args) to initialize the server, this must return {ok,
State}
and State becomes the initial state of the server.

Now mini_gs calls loop(Mod, State.) When mini_gs receives a message {From,
Tag, X}
it calls Mod:handle_call(X, From, State) which returns {repy, R, State1}. R1
is
sent back to the client, and the server calls loop/2 with the new state
State1.

That's it. call/2 is an interface routine to abstract out the interface
between the
client and the sever.

Now we can write a simple client application.

  -module(kv).

  %% These define the client API
  -export([start/0, store/2,lookup/1]).

  %% these must be defined because they are called by gs
  -export([init/1, handle_call/3]).

  -define(GS, mini_gs).
  %% -define(GS, gen_server).

  %% define the client API

  start()        -> ?GS:start_link({local,someatom}, kv, foo, []).
  store(Key,Val) -> ?GS:call(someatom, {putval,Key,Val}).
  lookup(Key)    -> ?GS:call(someatom, {getval,Key}).

  %% define the internal routines
  init(foo) -> {ok, dict:new()}.

  handle_call({putval, Key, Val}, _From, Dict) ->
     {reply, ok, dict:store(Key, Val, Dict)};
  handle_call({getval,Key}, _From, Dict) ->
    {reply, dict:find(Key, Dict), Dict}.


This module can call either gen_server or mini_gs (just change the define
statement)

So now we have turned a single process key-value store (using dict) into a
global
key-value store.  Note that kv.erl never uses the primitives spawn_link,
send, receive
or do on. ie kv.erl is written with pure *sequential* code.

This is *why* we made the gen_server abstraction. You can write well-typed
sequential code (the handle_call and init functions) to parametrize
a concurrent behavior, ie you need to know nothing about concurrency to
get the job done. We've "abstracted out" the concurrency.

Things become problematic when you do not entirely understand the
abstraction.
Maybe the abstraction is inappropriate for your needs. I have seen many
examples
of code where the gen_server *is* inappropriate. The acid test is "does the
gen_sever code look like spaghetti" if the answer is yes then all you have
done
is shoe horn the applications into an inappropriate form. In this case
you should ditch the gen_server and roll-your own.


/Joe
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20110420/61a600ef/attachment.htm>


More information about the erlang-questions mailing list