[erlang-questions] Server which spawns an additional server for each call

Dave Challis dsc@REDACTED
Wed Apr 20 12:52:58 CEST 2011


On 20/04/2011 09:11, Joe Armstrong wrote:
>
>
> On Tue, Apr 19, 2011 at 6:19 PM, Dave Challis <dsc@REDACTED
> <mailto:dsc@REDACTED>> wrote:
>
>     Hi Joe,
>     Fantastic, that's pretty much exactly what I was after.  I didn't
>     realise I could send a {noreply, X} from a handle_call, and defer
>     the actual reply from a spawned process.
>
>     The pure erlang version makes it clear what's going on too -
>     gen_server is still a bit of a black box to me I'm afraid.
>
>
> Oh dear - this frightens me - I've started a new thread to explain just
> exactly how simple the
> gen_server is - pleas read it

Thanks Joe, that's helped clear things up for me.  I've had a play round 
with the code, and managed to trace exactly what's going on now.

I'd only seen examples of code that used gen_server, so its actual 
behaviour was implied (and sometimes explained), but seeing exactly what 
its functions do makes such a difference (I'd somehow assumed it was a 
vastly complex piece of code - probably due to experience of servers in 
other languages!).

To be honest, the main examples of non-trivial erlang code that I've 
seen have come from the Erlang and OTP in Action book.  It frequently 
uses gen_server as a base for applications or functionality, so I 
assumed that it was good practice to do so.

A resource containing common design patterns for erlang would be ideal, 
but I'm not sure if such a thing exists yet!

>     The gen_server I'm using is a wrapper around a C utility which does
>     some parsing (using erlang's port mechanism), and then returns the
>     parsed data to the original function caller.
>
> Just to satisfy my curiosity,  why C? did you try this in pure Erlang first?

The main reason was so that I could re-use a fairly mature and fully 
featured 3rd party parsing library.  I'm just interested in working with 
the parsed data structures in erlang, the actual parsing itself isn't 
really what I'm focusing on.  It also seemed like a good learning 
opportunity to try out erlang ports with real code.

Dave

>
> /Joe
>
>     I figured that by having the port initialised when the gen_server
>     process is started, it could respond to client requests right away,
>     rather than being spawned upon request.
>
>     I was also thinking that by having the C program and port set up at
>     server start time, then any startup errors from it would be caught
>     then, rather than everything appearing to be ok until a client
>     request was made.
>
>     Thanks,
>     Dave
>
>
>     On 19/04/11 16:11, Joe Armstrong wrote:
>
>         It's not entirely clear to me what you want to do. I never
>         understand
>         why people use gen_servers
>         for everything, pure Erlang is often easier :-)
>
>         I assume you want to delegate the response in the server (ie get
>         some
>         other process than the
>         gen server to do the work, since this takes a long time)
>
>         One way to do this is like this:
>
>         Write a client stub like this:
>
>         foo(X) ->
>              gen_server:call(?Mod, {foo, X}).
>
>
>         Write a gen_server handle call method like this:
>
>         handle_call({foo, X}, From, State) ->
>              State1 = func1(X, State),
>              State2 = func2(X, State),
>              spawn_link(fun() -> do_something(State1, X, From) end),
>              {noreply, State2}.
>
>         do_something(State, X, From) ->
>              Reply = func3(X, State),
>              gen_server:reply(From, Reply).
>
>         here I've assumed func1 returns State1 and that State1 is some
>         subset of
>         State
>         needed in your computation. State2 (returned by func2) is the
>         continuation state of the server.
>         ie the state the server will have after it has received the foo
>         request,
>         but before the delegated function
>         has replied. You'll have to write func1 and func2 yourself.
>
>         do_something is the delegated function that might take a long
>         time. The
>         four lines of
>         code which define handle_call should return quickly, ie don't do
>         much
>         here, do the work in
>         do_something. handle call returns {noreply, State2} which means
>         "don't
>         reply to the client
>         but continue with state State2.
>
>         do_something works in parallel with the server and when it has
>         finished
>         calls gen_server:reply/2
>         and at this point the client stub routine  foo/1 will return.
>
>         This is only one way of doing this. You could do the complex
>         calculation
>         inside the client
>         and request the data you need from the server, the server can
>         spawn a
>         deligate (as above).
>         You can use a shared ets table and so on.
>
>         if you were to do this in pure erlang it might be easier to see
>         what's
>         going on
>
>         Define promise and yield thusly:
>
>         promise(Fun) ->
>                S = self(),                             %% this self() is
>         evaluated *inside* the current function
>                spawn(fun() ->
>                               S ! {self(), Fun()}    %% this self() is
>         evaluated
>         *inside* the spawned function
>                           end).
>
>         yield(Promise) ->
>               receive
>                    {Promise, Result} -> Result
>               end.
>
>         promise takes a Fun argument and returns a promise. The promise is a
>         promise to
>         compute the value. The promise can be redeemed by calling yield.
>         So we
>         can write code like this:
>
>         P = promise(fun() -> fib(40) end),
>         .... do some other stuff that takes a while ...
>         Val = yield(Promise)
>
>         So we compute fib(40) which takes a long time in parallel with some
>         other stuff.
>
>
>         The gen_server stuff above has just hidden what is essentially a
>         promise
>         and yield and
>         a bit of state trickery in a framework module - nothing tricky about
>         about it at all.
>
>         Given promise and yield and a dash of list comprehensions we can
>         write
>         fun stuff like:
>
>         parmap(F, L) ->
>               Promises = [promise(fun() -> F(I) end || I <- L],
>               [yield(I) || I <- Promises].
>
>         which is a parallel mapping function.
>
>         Hope this helps
>
>         /Joe
>
>
>
>         On Tue, Apr 19, 2011 at 10:58 AM, Dave Challis
>         <dsc@REDACTED <mailto:dsc@REDACTED>
>         <mailto:dsc@REDACTED <mailto:dsc@REDACTED>>> wrote:
>
>             Hi,
>             I'm trying to work out the structure for a small bit of
>             functionality in erlang.  I've got a feeling there's an obvious
>             solution, but I'm not experienced enough with the language
>         sure what
>             pattern to best follow...
>
>             So far, I've got a server which implements the gen_server
>         behaviour,
>             a supervisor for it, and a module containing an API for
>         interacting
>             with it (using gen_server:call mostly).
>
>             The server does a lot of work though, so blocks for a while
>         until
>             the call is finished.
>
>             What I'd like to do, is create a new server process each time
>             gen_server:call is invoked by a client, with each server
>         terminating
>             when it's done processing (but always leaving 1 server running).
>
>             This means that clients using the API will have their request
>             processes straight away, without having to wait for all the
>         other
>             calls to the server to finish.
>
>             I can't quite figure out where to place the logic for doing
>         this though.
>
>             * Should the API module ask the supervisor to spawn a new child,
>             then send the client's call to this?
>             * Should API calls go to the supervisor, and have it decide
>         whether
>             to spawn new servers or send the call to an existing one?
>             * Or should each server take care of telling the supervisor
>         to spawn
>             a new child, and pass the call request to the newly spawned one?
>
>             Is this a sensible approach in general, or is there an obvious
>             pattern or some functionality I've missed?
>
>             Thanks,
>
>             --
>             Dave Challis
>         dsc@REDACTED <mailto:dsc@REDACTED>
>         <mailto:dsc@REDACTED <mailto:dsc@REDACTED>>
>             _______________________________________________
>             erlang-questions mailing list
>         erlang-questions@REDACTED <mailto:erlang-questions@REDACTED>
>         <mailto:erlang-questions@REDACTED
>         <mailto:erlang-questions@REDACTED>>
>
>         http://erlang.org/mailman/listinfo/erlang-questions
>
>
>
>
>     --
>     Dave Challis
>     dsc@REDACTED <mailto:dsc@REDACTED>
>
>




More information about the erlang-questions mailing list