Joe's "deep trickery"

Chris Pressey <>
Fri Feb 28 09:03:24 CET 2003


Good day, eh?

OK, in Joe's latest tutorial
(http://www.sics.se/~joe/tutorials/web_server/web_server.html) he states:

"We also need some deep trickery to arrange that one
instance of this process is started for each incoming request"

And by the looks of tcp_server.erl, the trickery is quite complex.

But I've been using a very much simpler method for starting generic TCP/IP
servers, roughly:

  server(Module, Function, Args, Port, Options) ->
    case gen_tcp:listen(Port, Options) of
      {ok, LSock} ->
        spawn_link(?MODULE, server_loop,
          [Module, Function, Args, LSock, Port, Options])
    end.

  server_loop(Module, Function, Args, LSock, Port, Options) ->
    case gen_tcp:accept(LSock) of
      {ok, Socket} ->
         Pid = spawn_link(Module, Function, [Socket] ++ Args),
         gen_tcp:controlling_process(Socket, Pid),
         server_loop(Module, Function, Args, LSock, Port, Options);
      {error, closed} ->
         server(Module, Function, Args, Port, Options);
      N ->
         server_loop(Module, Function, Args, LSock, Port, Options)
    end.

I realize my version doesn't limit the number of connections like Joe's
does, but that seems straightforward to implement.  Also, if you want
{active, true}, you have to pass the option {active, false} initially,
then use inet:setopts to set it to {active, true} while in
Module:Function, or risk a race condition - a minor inconvenience.

This version seems to work ok for me, and even with those improvements, it
could hardly be called deep trickery; so in deference to Joe's wisdom, and
knowing how much of a fan he is of simplicity, I must assume either:

a) mine has a fatal flaw or flaws that I can't presently perceive, or
b) Joe forgot the word "efficiently" before "arrange" in his sentence

My concern is mainly that an intimidating module like tcp_server.erl could
scare off new Erlang programmers by giving them the impression that
working with TCP/IP in Erlang is a bit nasty.  It's not nasty at all, at
least I don't think it is unless you want to push the envelope.

This is otherwise a fine tutorial.  I like it.

Middlemen gets me thinking: are there generic consumer/producer patterns
that can be packaged?  I find I'm writing a lot of processes along the
lines of: accept data in arbitrary-sized chunks from one process and
dispense it in chunks of some calculated size to another process,
sometimes reformatting it on the way.  Is there something like a
gen_stream that I've overlooked in OTP?

But then I start thinking: why the hell do I want more gen_* modules when
I rarely ever use the existing ones?  For better or worse, I usually build
my own with receive and !, which I find easier to read (at least while
coding,) with the assumption that some day, if it becomes really
important, I'll rewrite them to be gen_*'s.  So I sat myself down the
other day and forced myself to write one each gen_server, gen_event and
gen_fsm, to practice.

I learned more about them, but unfortunately I learned more about why I
never end up using them.  I totally understand Suresh's confusion over
gen_event:call.  It's unintuitive until you think about how Erlang grew up
- in an ideal world, if you wanted an event handler you could treat like a
server, you'd say -behaviour([gen_event, gen_server]).  Clearly, you can't
do that, and gen_event:call smells like the workaround for it.

Also, it would be really nice if the event handler could actually *handle*
events and not just react to them after they've happened - i.e. if
gen_event:notify took, and returned, a term to represent state, perhaps
modified by the event handlers.  (That way you wouldn't need
gen_event:call either; you could configure an event handler using events.)

Anyway, sorry that got off on sort of a tangent.

-Chris



More information about the erlang-questions mailing list