[iolib-devel] Broken code

Peter Keller psilord at cs.wisc.edu
Tue Dec 29 18:41:13 UTC 2009


Hello,

I've attached an example program, loadable with (load "server.lisp"),
and runnable with (start-server). It uses ioib and alexandria.

Once the server is started, one can 'telnet localhost 10000'. As soon as
this happens you will see lisp forms automatically sent to the client,
plus a timeout form every sixty seconds. The server will emit some
information about the stream it is using before sending the data.

To see the bug, ctrl-} the telnet session on the client, and close
it with 'close'.  There should be an immediate error on the server.
This happens for me 100% of the time.

Thank you.

-pete
-------------- next part --------------
; sbcl.28005

(eval-when (:compile-toplevel :load-toplevel :execute)
  (asdf:oos 'asdf:load-op :iolib.sockets))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (asdf:oos 'asdf:load-op :alexandria))

(use-package 'common-lisp)
(use-package 'iolib.sockets)
(use-package 'alexandria)

(defparameter *event-base* nil)
(defparameter *sockets* nil)
(defparameter *port* 10000)
(defparameter *count* 0)
(defvar *streams* nil)

(defun start-server (&key (host iolib.sockets:+ipv4-unspecified+) (port *port*) (timeout (* 60 60 24)))

  (setf *streams* (make-hash-table :test #'equal))

  (let ((iolib.sockets:*ipv6* nil))
       (unwind-protect
        (progn
         (setf *event-base* (make-instance 'iomux:event-base))
         (format t "About to start-my-server~%")
         (with-open-stream (sock (start-my-server host port))
                           (declare (ignorable sock))
                           (format t "Starting event-dispatch...~%")
                           (do ()
                               (nil nil)
                               ;; here is where I should stick one main loop
                               ;; iteration and then N i/o iterations.
                               (stdout-streams)
                               (transmit-work `(work ,(get-universal-time)))
                               (iomux:event-dispatch *event-base*
                                                     :timeout .10
                                                     :one-shot t))
                           (format t "Ending event-dispatch...~%")))
        (format t  "No new clients in ~A seconds: timed out. Terminating." 
                timeout)
        (close-all-sockets)
        (close *event-base*))))

(defun close-all-sockets ()
  (setf *streams* (make-hash-table :test #'equal))
  (map 'nil #'close-socket *sockets*))

(defun start-my-server (host port)
  (format t "Start-my-server called: ~A ~A~%" host port)
  (let ((socket
         (iolib.sockets:make-socket 
          :connect :passive 
          :address-family :internet
          :type :stream 
          :local-host host
          :local-port port 
          :backlog 5 
          :reuse-address t
          :external-format '(:utf-8 :eol-style :crlf)
          :ipv6 nil)))

       (format t "socket bound: ~A~%" socket)
       (setf *sockets* nil)
       (unwind-protect-case ()
                            (progn
                             (format t "Setting socket to nonblocking~%")
                             (setf (iolib.streams:fd-non-blocking socket) t)
                             (format t "set-io-handler called...~%")
                             (iomux:set-io-handler
                              *event-base*
                              (iolib.sockets:socket-os-fd socket)
                              :read 
                              (make-listener-handler socket)
                              :timeout 10))
                            (:abort 
                             (close socket)))
       (format t "start-my-server returning a socket!~%")
       socket))

(defun make-listener-handler (socket)
  (format t "Making listener handler...~%")
  #'(lambda (fd event exception)
            (declare (ignore fd event))
            (block nil
                   (when (eql :timeout exception)
                         (warn "Got a server timeout: ~A (time ~A)!" *count*
                               (get-universal-time))
                         (incf *count*)
                         (transmit-work '(a server timeout))
                         (return))
                   (let ((client (iolib.sockets:accept-connection socket)))
                        (format t "accepted connection: ~A~%" client)
                        (when client
                              (format t "set client to nonblocking~%")
                              (setf (iolib.streams:fd-non-blocking client) t)
                              (add-socket client)
                              (format t "Accepted a client~%")
                              (serve client))))))


(defun serve (socket)
  (format t "Serving the client and setting up i/o handler~%")
  (iomux:set-io-handler *event-base*
                        (iolib.sockets:socket-os-fd socket)
                        :read
                        (make-conversation socket (make-disconnector socket))))


(defun make-disconnector (socket)
  #'(lambda ()
            (format t "Disconnecting a socket~%")
            (remove-stream socket)
            (close-socket socket)))

(defun make-conversation (stream disconnector)
  (format t "Making an conversation function.~%")
  (add-stream stream)
  #'(lambda (fd event exception)
            (declare (ignore fd event exception))
            (handler-case
             (conversation stream disconnector)
             (end-of-file ()
                          (remove-stream stream)
                          (funcall disconnector)))))

;; read a lisp form from the stream and write 'ok back to the stream.
(defun conversation (stream disconnector)
  (let ((*read-eval* nil))
       (let ((form (read stream)))
            (cond ((equal form '(quit))
                   (funcall disconnector))
                  (t
                   (format t "Form from Client: ~A~%" form)
                   (format stream "~s~%" 'OK)
                   (ignore-some-conditions
                    (iolib.streams:hangup)
                    (finish-output stream)))))))


(defun close-socket (socket)
  (format t "close-socket: ~A~%" socket)
  (let ((fd (iolib.sockets:socket-os-fd socket)))
       (ignore-some-conditions (isys:syscall-error)
                               (iomux:remove-fd-handlers *event-base* fd))
       (remove-socket socket)
       (close socket)))

(defun remove-socket (socket)
  (format t "remove-socket ~s~%" socket)
  (removef *sockets* socket))

(defun add-socket (socket)
  (format t "add-socket ~s~%" socket)
  (push socket *sockets*))

(defun remove-stream (stream)
  (format t "remove-stream [count: ~A] ~s~%" (hash-table-count *streams*) stream)
  (remhash stream *streams*))

(defun add-stream (stream)
  (format t "add-stream [count: ~A] ~s~%" (hash-table-count *streams*) stream)
  (setf (gethash stream *streams*) stream))

(defun stdout-streams ()
  (when (> (hash-table-count *streams*) 0)
        (maphash
         #'(lambda (k v) 
                   (format t "State of stream ~s is ~s~%" k v))
         *streams*)))


(defun transmit-work (form)
  (when (> (hash-table-count *streams*) 0)
        (maphash
         #'(lambda (k v) 
                   (declare (ignore k))
                   ;(format t "Transmitting to ~s~%" k)
                   (format v "~s~%" form)
                   (finish-output v)
         *streams*)))

;; commented out.
;;(handler-bind
;;  ((isys:eintr (lambda (e) (invoke-restart
;;                            'ignore-syscall-error))))
;;  (send-to socket (cl-serializer:serialize 42)
;;           :remote-host "hostname" :remote-port
;;           9999))







More information about the iolib-devel mailing list