Re: ANN: Etiquette: a protocol construction tool

Luke Gorrie <[email protected]> 18 Sep 2003 05:44:49 +0200
Newsgroups gmane.lisp.clump
Message-ID <[email protected]>
Hi Eugene, thanks for the details.

> Luke>  Multitasking. One thread should be able to run many state
> Luke>  machines, so that when one blocks on I/O another is allowed to
> Luke>  execute.
> 
> I don't quite understand how can execution proceed in the same context
> when blocking input operation is performed.

[This turned out to be a long post..]

There are two contexts: the Lisp thread and the state machine being
executed. I want to run many state machines with a single Lisp
thread. One state machines runs at a time, but when it needs input it
is "scheduled out" and another one is executed instead.

Here's the pitch:

  Programs are simpler to write using blocking I/O than non-blocking.

  Non-blocking I/O is more scalable than blocking because it only
  needs a fixed number of threads (e.g. one).

  Frameworks like Etiquette can combine the best of both worlds by
  providing a blocking programming interface that is implemented with
  non-blocking I/O under the hood. They do this by writing a custom
  state-machine scheduler based on a "select() loop".

So you could add "blocking" read and write primitives to your
interpreter. If a state machine tries to do I/O that would block, you
capture its state and save it, then use the SERVE-EVENT select()-loop
to find out when the input is available and the machine can
continue. So the state machines are written as if they have their own
thread, but are all multiplexed onto one actual Lisp thread.

That might seems abstract, but it solves a very real problem. To get
adequate performance and robustness, a server for most protocols
_must_ handle many clients at once. Consider the problems of an SMTP
server that only serves one client at a time:

  If you do 10ms worth of processing for each message, and each
  request takes on average one second due to network latency (there
  are several round-trips), then you only spend 1% of your time doing
  actual work.

  If a client doesn't respond, e.g. because he fell off the network,
  then you are paralysed until you detect a timeout. That can take
  e.g. 30 seconds.

One solution to this is to use a select()-loop and non-blocking
I/O. This is tricky to code, but can give you great scalability. You
can talk to a thousand clients at once, and the state record for each
is just a small data structure.

Another is to use multithreading. With one thread per client, you can
use the convenient blocking-I/O interface and avoid stalling. But
threads are typically expensive, and you cannot just create a thousand
of them and still get good performance. (Not necessarily so, but I
don't think current Lisps implement very light-weight threading -- if
they do please tell me!)

A variation is to have a thread pool of a fixed maximum size, but this
is not very robust. It assumes you will never have to block on more
than N connections at a time, which breaks down with slow/unreliable
clients and with DoS attackers.

The _right_ solution is to have a system that lets you write simple
code and have it executed in a select()-loop. Then you get the nice
programming interface plus the scalability and robustness. In my
opinion implementing such an interface is _the_ most important thing
for Etiquette-like frameworks.

My perspective may be warped of course. :-)

For more information about the scalability issues, check out the "C10K
problem" page:

  http://www.kegel.com/c10k.html

Note: it's now clear that the world will never notice that Erlang has
solved all this stuff to a tee. We've been merrily using this fact
against our competitors for years :-)

Cheers,
Luke