jarohen / chord

A library designed to bridge the gap between the triad of CLJ/CLJS, web-sockets and core.async.
439 stars 40 forks source link

Tab hangs the tab when connection is broken #24

Closed nidu closed 9 years ago

nidu commented 9 years ago

I have an issue with the lib. There's a simple code handling websocket messages from server.

; if response is neither nil nor error - put it into another chan
(defn handle-server-msg [{:keys [error message] :as resp} handler-ch]
  (when resp
    (println "Received from server" resp)
    (if error
      (println "Got error from server" error)
      (let [{:keys [messages]} message]
        (doall (map #(put! handler-ch %) messages))))))

; install receiver which just calls function above
(defn install! [handler-ch]
  (go
    (let [ws-url (str "ws://" (.-host js/location) "/sync")
          {:keys [ws-channel error] :as r} (<! (ws-ch ws-url {:format :json}))]
      (if error
        (println "Error opening websocket" error)
        (do
          (set! *ws-chan* ws-channel)
          (go-loop []
            (handle-server-msg (<! *ws-chan*) handler-ch)
            (recur)))))))

It works fine but when i turn down the server - chrome tab just hangs. Chrome task manager shows CPU consumption for this tab around 100% (Mac). It looks like ws-channel when connection breaks infinitely returns nil (tried println in first line of handle-server-msg) and it causes tab to hang.

How should i handle cases like this? Maybe introduce some timer on receiving nil and query chan on timer tick? Thanks in advance!

jarohen commented 9 years ago

Hi Nikolay,

When the connection closes, Chord closes the websocket channel (IIRC, with an error message, if it closes unexpectedly?). Whenever a core.async channel is closed, any attempts to take a value from it will return nil, so (as you suspected) your go-loop here is repeatedly taking nil, and passing it to handle-server-msg.

Fortunately, core.async guarantees that the only time that you receive nil from a channel is when the channel is closed - you can't normally put nil onto a channel - so you can check for nil here, and iff you get one, the connection has closed. I often see this implemented in core.async using a 'when-let', as follows:

(go-loop []
  (when-let [msg (a/<! my-channel)]
    (process-message! msg)
    (recur)))

Let me know if this helps!

Cheers,

James

nidu commented 9 years ago

James, thanks a lot! Yeap, i should look at core.async closer. After all i made reconnecting channel with setInterval so know it seems to work fine.

jarohen commented 9 years ago

Cool - thanks for letting me know!