Have been having a lot of fun with core-async recently. Here's a wrapper around sockets -- would love your feedback if you get time!
(defn start-socket [{:keys [id ws-conn send-ch receive-ch cleanup-ch
start-receive-worker]
:as socket}]
(log/infof "started socket id = %s" id)
(let [send-worker-ch (start-send-worker socket) ;; returns a go-loop
receive-worker-ch (start-receive-worker socket)] ;; returns a go-loop
;; trap if these workers ever fail
(a/go
(let [[e] (a/alts! [send-worker-ch receive-worker-ch])]
(when (instance? Throwable e)
(log/error e "worker errored id = %s" id)
(a/close! cleanup-ch)))))
;; cleanup
(a/go
(a/<! cleanup-ch)
(log/infof "cleaning up socket id = %s" id)
(remove-socket! id)
(a/close! send-ch)
(a/close! receive-ch)
(when (.isOpen ws-conn)
(.close ws-conn))))
(defn create-undertow-config [{:keys [id start-receive-worker]}]
(let [send-ch (a/chan)
receive-ch (a/chan)
cleanup-ch (a/chan)]
{:undertow/websocket
{:on-open (fn [{ws-conn :channel}]
(start-socket {:id id
:ws-conn ws-conn
:send-ch send-ch
:receive-ch receive-ch
:cleanup-ch cleanup-ch
:start-receive-worker start-receive-worker}))
:on-message (fn [{:keys [data]}]
(a/>!! receive-ch (<-json data true)))
:on-error (fn [{throwable :error}]
(condp instance? throwable
java.net.SocketException nil
(log/error throwable "error")))
:on-close-message (fn [{:keys [message]}]
(log/infof "close-message received id = %s message = %s"
id (bean message))
(a/close! cleanup-ch))}}))
Most of the work happens in start-socket.
Main thing I don't like right now:
(let [send-worker-ch (start-send-worker socket) ;; returns a go-loop
receive-worker-ch (start-receive-worker socket)] ;; returns a go-loop
;; trap if these workers ever fail
(a/go
(let [[e] (a/alts! [send-worker-ch receive-worker-ch])]
(when (instance? Throwable e)
(log/error e "worker errored id = %s" id)
(a/close! cleanup-ch)))))
If workers ever fail, they stop and return an exception. This alts! is checking if these workers ever stop b/c of an exception. I wonder if there's an abstraction I should create here (something like trap-first-err , but am not sure)For posterity: I wrote up a wait? abstraction, inspired by more.async. It waits for all channels to close, but if any channel returns an exception, it re-throws it.