multithreadingsocketsconcurrencycommon-lispccl

Spawning multiple processes to handle many socket based connections in Clozure Common Lisp


I have the following:

(defun serve (&key (port 80) (handler #'IDENTITY))
  (WITH-OPEN-SOCKET 
    (socket :LOCAL-PORT port 
            :LOCAL-HOST "localhost" 
            :CONNECT :PASSIVE 
            :REUSE-ADDRESS t)
    (flet ((handle-connection ()
                              (with-open-stream 
                                (stream (ACCEPT-CONNECTION socket :wait t))
                                (funcall handler stream))))
          (loop (handle-connection)))))

When a connection comes in, it is accepted and a stream is passed to the handler. The process waits (blocks) on the handler. So the next connection is processed when handler returns.

The workaround is to have one process(/thread) per connection, so a new connection doesn't have to wait for the handler to finish processing on the earlier connection.

I tried by doing:

(PROCESS-RUN-FUNCTION (gensym) 
  (lambda () (funcall handler stream)))

in place of just (funcall handler stream), but this ends up erroring out, because the stream is not available by the time the handler gets called. Obviously because with-open-stream has exited by that time, and stream is out of scope and thus (maybe?) GC'd.

Then I tried:

(loop 
  (PROCESS-RUN-FUNCTION (gensym) 
    (lambda () 
      (format t "new process ") 
      (handle-connection))))

instead of just (loop (handle-connection)), which runs away spawning new processes at the speed of loop, because the waiting on socket part, doesn't block the execution anymore.

What is the right way to create separate threads/processes to handle many connections on the same socket?


Solution

  • You should not work with a stream, while you are concurrently closing it.

    You also should not use variables in the process, which get values from the outside of the process. Pass all data you need to the function, which is running as a process, via arguments.

    (let ((accepted-stream (acception-connection socket      ; just take the stream
                                                 :wait t)))
    
      (process-run-function               ; run a function as a process/thread
    
        (gensym "CONNECTION-HANDLER-")    ; generate a name
    
        (lambda (stream)                  ; take the one argument, the stream
          (unwind-protect (progn ...)     ; do something, with clean-up
            (close stream)))              ; make sure the stream gets closed
    
        accepted-stream))                 ; pass the stream, which then gets passed to
                                          ; the lambda function