From: Steve Smith
Subject: Async and single threading sockets in SBCL
Date: 
Message-ID: <87r7h4shsd.fsf@lucretia.isay.com.au>
Hi,

I'm a lisp newbie writing a server in SBCL and I would prefer to make
it non-blocking/single-threaded if at all possible.  This isn't merely
a dislike of threads (although that's part of it) but also to do with
the fact that I need the threads to be able to pass messages between
each-other, and that is complicated in multithread design (as the
thread needs to watch both the incoming socket and the inter-thread
queues).  Can someone with more knowledge of this explain the current
preferred method of single-threaded servers in lisp (SBCL specific if
necessary), or provide pointers to a system that works in this manner?

Thanks,
Steve

From: Steve Smith
Subject: Re: Async and single threading sockets in SBCL
Date: 
Message-ID: <87zmvsr2yg.fsf@lucretia.isay.com.au>
"Steve" == Steve Smith <·····@internode.on.net> writes:
> Hi, I'm a lisp newbie writing a server in SBCL and I would prefer to
> make it non-blocking/single-threaded if at all possible.

I should probably also point out that connections to the server are
intended to be long-lived, so some method of multiplexing the
connections is necessary (i.e. something like async/select), rather
than just serving and dropping the connection in a loop.

Thanks,
Steve
From: Paul Khuong
Subject: Re: Async and single threading sockets in SBCL
Date: 
Message-ID: <a828a711.0504211707.244b2d84@posting.google.com>
Steve Smith <·····@internode.on.net> wrote in message news:<··············@lucretia.isay.com.au>...
> "Steve" == Steve Smith <·····@internode.on.net> writes:
> > Hi, I'm a lisp newbie writing a server in SBCL and I would prefer to
> > make it non-blocking/single-threaded if at all possible.
> 
> I should probably also point out that connections to the server are
> intended to be long-lived, so some method of multiplexing the
> connections is necessary (i.e. something like async/select), rather
> than just serving and dropping the connection in a loop.
> 
I'd take a look at this document: http://www.lisp-p.org/tcpmux/, which
describes a single threaded server (with multiple connections) in
CLISP. Corresponding/close enough I/O functions should exist for SBCL
too.
"I think threads are over-used. They are too often used at the expense
of simplicity & maintainability. In fact, threads are often used at
the expense of correctness.

A single thread can achieve most of the performance you can achieve
with multiple threads unless you have multiple CPUs and you know what
you are doing with multiple-threads. Multiple CPUs are fairly common
these days, but most humans can't do a very good job with multiple
threads.

I've written single-threaded servers that support multiple connections
& have good performance, but until now, they've all been owned by my
employers. I wanted to implement such a server that was free
software4.1 as proof that single-threaded network servers work just
fine. So my second requirement was ``Use a single thread because I
want to demonstrate that it can be done with a single thread''. "

There is both code and documenttion. Even if you don't use the task
switcher (which I've been wanting to put to work along with UCW's cps
transform), the rest should be there.

Paul Khuong
From: Marcin 'Qrczak' Kowalczyk
Subject: Re: Async and single threading sockets in SBCL
Date: 
Message-ID: <87sm1jrszh.fsf@qrnik.zagroda>
·······@gmail.com (Paul Khuong) writes:

> I'd take a look at this document: http://www.lisp-p.org/tcpmux/, which
> describes a single threaded server (with multiple connections) in
> CLISP. Corresponding/close enough I/O functions should exist for SBCL
> too.
> "I think threads are over-used. They are too often used at the expense
> of simplicity & maintainability. In fact, threads are often used at
> the expense of correctness.

I disagree.

> A single thread can achieve most of the performance you can achieve
> with multiple threads unless you have multiple CPUs and you know what
> you are doing with multiple-threads. Multiple CPUs are fairly common
> these days, but most humans can't do a very good job with multiple
> threads.

This is a separate issue: it may look as threads to the programmer yet
be implemented using a single OS thread. But I believe threads are
usually clearer to a programmer than manual context switching.

Here isa part of tcpmux implementation of a function which sends a
help text in the response to the "help" request:

(defmethod done-writing ((self help))
  (cond ((lst self)
         ;; There's something to write, so we queue it & an ASCII
         ;; CR LF pair after it.
         (enqueue-write self (pop (lst self)))
         (enqueue-write self '(13 10)))
        (t
         ;; There's nothing more to write, so we bug out.
         (remove-connection self :close t)))
  self)

Note that it materializes the current continuation (which in this case
corresponds to a list of lines to be written). This approach is awful
for a complex control flow of a single connection. You cannot call a
function which writes something to a socket. Instead you must store
the state in an object and return, and you will be called back when
the write completes.

Similarly for reading. You don't call a function, but your class
provides an implementation of the generic function nread, which is
called when the next character is available. This forces to dispatch
all actions done after reading from a signle point of code.

> I've written single-threaded servers that support multiple connections
> & have good performance, but until now, they've all been owned by my
> employers. I wanted to implement such a server that was free
> software4.1 as proof that single-threaded network servers work just
> fine. So my second requirement was ``Use a single thread because I
> want to demonstrate that it can be done with a single thread''. "

Looking at the implementation of tcpmux, it processes a single
character at a time between returning to the event loop. One iteration
of an event loop makes work proportional to the number of active
connections, in particular it conses 3 new lists of that size. Inside
a single connection, a buffer of bytes to write is represented as a
list of integers, which is repeatedly appended at the end.

I don't believe that this implementation is efficient for non-toy
usages. It could be written with much better quality even while
keeping this style. And as I said above, IMHO this style is awful.

-- 
   __("<         Marcin Kowalczyk
   \__/       ······@knm.org.pl
    ^^     http://qrnik.knm.org.pl/~qrczak/
From: Steve Smith
Subject: Re: Async and single threading sockets in SBCL
Date: 
Message-ID: <87vf6dd7np.fsf@lucretia.isay.com.au>
> I'd take a look at this document: http://www.lisp-p.org/tcpmux/,
> which describes a single threaded server (with multiple connections)
> in CLISP.

Thanks Paul.  I did indeed look (and still am) at this.  However it
appears to rely on a custom C extension, and I wondered if that was
still necessary.

Cheers,
Steve
From: Thomas F. Burdick
Subject: Re: Async and single threading sockets in SBCL
Date: 
Message-ID: <xcvll7b9s1h.fsf@conquest.OCF.Berkeley.EDU>
Steve Smith <·····@internode.on.net> writes:

> Hi,
> 
> I'm a lisp newbie writing a server in SBCL and I would prefer to make
> it non-blocking/single-threaded if at all possible.  This isn't merely
> a dislike of threads (although that's part of it) but also to do with
> the fact that I need the threads to be able to pass messages between
> each-other, and that is complicated in multithread design (as the
> thread needs to watch both the incoming socket and the inter-thread
> queues).  Can someone with more knowledge of this explain the current
> preferred method of single-threaded servers in lisp (SBCL specific if
> necessary), or provide pointers to a system that works in this manner?

You picked the right Lisp for your question: SERVE-EVENT is an
event-server architecture that comes with SBCL (and CMUCL).  It's
quite nice, and when used properly can make for nice, performant
servers on uniprocessor machines.  You don't have to deal with a lot
of the issues you get with threads, like synchronization issues.
Nothing's free, though, and a reentrant event server has some issues
of its own.  In particular, you need to be concious of the stack: what
are the dynamic bindings, what condition handlers are active, etc.  If
you use a nonlocal exit, you need to be careful that you're not
unwinding another request-handler's portion of the stack.  You want to
make sure that a high level of incomming requests (a) doesn't cause
you to overflow the stack, and (b) doesn't prevent you from finishing
your response to earlier requests.

In exchange for this, you get a single thread which can register
handlers with input sources (Unix file descriptors or X11 events).
Anything that calls SERVE-EVENT (this includes the REPL) listens for
input for all handlers.  When a handler is called, it does some
processing, possibly sending input back to the client, possibly
registering other handlers (to delay some of that processing),
possibly calling SERVE-EVENT directly, to give other handlers a chance
to run while it waits for something, and possibly making blocking I/O
calls.  In the last case, assuming your streams are buffered properly,
the system's blocking I/O functions will call SERVE-EVENT if they need
to block.

You can search this newsgroup for old discussions about SERVE-EVENT,
and it's covered in the CMUCL Manual.