websocket-driver icon indicating copy to clipboard operation
websocket-driver copied to clipboard

Making sending from a separate thread work when using Woo as a backend server

Open svetlyak40wt opened this issue 7 years ago • 10 comments

In current websocket-driver there is a problem – when you start a separate thread on the server and trying to send message to the client, it is delivered with delay after only 60 seconds. The issue exists only when using Woo server. On Hunchentoot, message is delivered immediately.

The problem is the libev's event loop. This commit is a hack around and should be improved to work with all backend servers.

svetlyak40wt avatar May 03 '18 17:05 svetlyak40wt

@fukamachi any help from you will be very appreciated here.

I'm trying to use websocket-driver with (new) version of the Weblocks framework. And want to push data to the client from a separate background thread. But this does not work with Woo server.

This patch is a workaround which works for me but should be improved before merge.

svetlyak40wt avatar May 03 '18 17:05 svetlyak40wt

Eitaro, what do you think?

svetlyak40wt avatar May 12 '18 08:05 svetlyak40wt

Thank you for reporting. I'm still really not sure, but perhaps this should be done somehow in Woo socket code. I'll look into it and try to fix it.

fukamachi avatar May 12 '18 23:05 fukamachi

Can you retry it with Woo's allow-multithread-write branch?

fukamachi avatar May 13 '18 03:05 fukamachi

Can you retry it with Woo's allow-multithread-write branch?

No, does not work. Nothing changed.

svetlyak40wt avatar May 14 '18 17:05 svetlyak40wt

I wrote the reproducible sample code. And allow-multithread-write branch just works expected.

% sbcl --load this.lisp

;;% uname -rpms
;;Darwin 18.2.0 x86_64 i386

(ql:quickload
 '("clack"
   "websocket-driver"
   "woo"))

(defparameter *port* 8000)
(defparameter *server*  ;; please change this to switch another backend.
  :woo
  ;:hunchentoot
  )

(use-package :websocket-driver)

(defvar *echo-server*
  (lambda (env)
    (let ((ws (make-server env)))
      (on :message ws (lambda (message)
                        ;; The code simulates sending message from another thread context.
                        (bt:make-thread (lambda () (send-text ws message)))))
      (lambda (responder)
        (declare (ignore responder))
        (start-connection ws)))))

;; Client
(bt:make-thread
 (lambda ()
   (sleep 3)

   (let ((ws (make-client (format nil "ws://localhost:~D/" *port*))))
     (start-connection ws)
     (on :message ws (lambda (message)
                       (format t "Receive ~A~%" message)))
     (send ws "Hi"))))

;; Server
(clack:clackup *echo-server* :server *server* :port *port*)

kkazuo avatar Nov 28 '18 13:11 kkazuo

Great, will try it again soon. Thank you!

svetlyak40wt avatar Nov 28 '18 14:11 svetlyak40wt

Another workaround (SBCL only)

(sb-ext:schedule-timer (sb-ext:make-timer (lambda ()
                                            (wsd:send-text ws "message"))
                                          :thread th)
                       0)

with ws is websocket object and th is thread (bt:current-thread) with the time you'v been call wsd:make-server.

The above code can run on current master branch of websocket-driver.

kkazuo avatar Nov 28 '18 15:11 kkazuo

@kkazuo interesting feature! I didn't know about it! Thank you.

svetlyak40wt avatar Apr 25 '19 18:04 svetlyak40wt

INTERRUPT-THREAD and SCHEDULE-TIMER are unsafe unless it is known that injecting code into an executing thread is okay. In this case, this would require libev to be reentrant among other things. Using a queue, as svetlyak40wt does, seems inevitable.

melisgl avatar Oct 21 '20 08:10 melisgl