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

Håkan Mattsson hm@REDACTED
Thu Apr 21 16:32:40 CEST 2011

On Wed, Apr 20, 2011 at 10:05 AM, Joe Armstrong <erlang@REDACTED> wrote:
> 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.

I tend to use proc_lib (spawn_link, start_link, init_ack) and sys
(handle_system_msg) a lot. They gives you almost all the bells and
whistles that gen_server has, but it enables you to have the same
simple code structure that you have in your very pedagogic code
snippet below.



  By the way, it seems more appropriate to use spawn_link in a
  function called start_link.

> 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 messag
> {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

More information about the erlang-questions mailing list