Consequences of not closing channels in browser?

449 views
Skip to first unread message

Roberto Oliveros

unread,
Jul 11, 2014, 9:24:33 PM7/11/14
to clojur...@googlegroups.com
I'm using Om for the client side and through the lifetime of the application many components gets mounted/unmounted. When mounted, various channels are opened (in go blocks). And I'm planning to use IWillUnmount to close them too. But first, my questions are: What happens to unclosed channels? Do the resources they used get released? Not closing channels (when unmounting components) can degrade the browser performance in the longrun? Thanks.

Daniel Kersten

unread,
Jul 12, 2014, 11:46:41 AM7/12/14
to clojur...@googlegroups.com
Channels are cheap, but unless they get garbage collected (and I assume not closing them will prevent this), they will still take up some resources.

Related and important is that you shut down any go blocks that you create in IWillMount, especially if they take from a channel which may still be receiving data (eg: through mux or pub/sub). If you don't shut these down, they will continue to run even after the component has unmounted and if the component is later mounted again, you will have multiple go blocks doing the same thing! Besides using resources, this can actually be dangerous if you are accessing owner inside the go block (eg to set local state) as you may end up accessing an unmounted owner. The simplest solution is having a kill channel that you listen on using async/alt that gets closed in the IWillUnmount and that terminates the go block.


On 12 July 2014 02:24, Roberto Oliveros <roberto...@mac.com> wrote:
I'm using Om for the client side and through the lifetime of the application many components gets mounted/unmounted. When mounted, various channels are opened (in go blocks). And I'm planning to use IWillUnmount to close them too. But first, my questions are: What happens to unclosed channels? Do the resources they used get released? Not closing channels (when unmounting components) can degrade the browser performance in the longrun? Thanks.

--
Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to the Google Groups "ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at http://groups.google.com/group/clojurescript.

Message has been deleted

Mike Fikes

unread,
Jul 12, 2014, 1:59:54 PM7/12/14
to clojur...@googlegroups.com
I would imagine Brandon Bloom's response to a similar question in the Clojure group is applicable to the ClojureScript implementation of of core.async:

https://groups.google.com/d/msg/clojure/Zr1FmmE2cpQ/B_6MV2sTP8wJ

Dhruv Bhatia

unread,
Aug 30, 2014, 11:08:36 AM8/30/14
to clojur...@googlegroups.com
On Sunday, 13 July 2014 01:46:41 UTC+10, Daniel Kersten wrote:
> Channels are cheap, but unless they get garbage collected (and I assume not closing them will prevent this), they will still take up some resources.
>
>
> Related and important is that you shut down any go blocks that you create in IWillMount, especially if they take from a channel which may still be receiving data (eg: through mux or pub/sub). If you don't shut these down, they will continue to run even after the component has unmounted and if the component is later mounted again, you will have multiple go blocks doing the same thing! Besides using resources, this can actually be dangerous if you are accessing owner inside the go block (eg to set local state) as you may end up accessing an unmounted owner. The simplest solution is having a kill channel that you listen on using async/alt that gets closed in the IWillUnmount and that terminates the go block.

Thanks for the explanation. Would you be able to provide a code example that demonstrates how to properly clean up a pub/sub watcher within a component's IWillUnmount lifecycle method?

Daniel Kersten

unread,
Aug 30, 2014, 9:24:19 PM8/30/14
to clojur...@googlegroups.com
Something like this:

(defn component [data owner]
  (reify
    om/IInitState
    (init-state [_]
      (let [chan (chan)]
        {:chan chan
         :kill (chan)
         :pub  (pub chan first)}))

    om/IWillMount
    (will-mount [_]
      (let [pub (om/get-state owner :pub)
            chan (chan)
            kill (om/get-state owner :kill)]
        ; If using pub/sub, need to subscribe
        (sub pub :foo chan) 
        (go-loop []
          (let [[v c] (alts! [chan kill])]
            (when-not (= c kill)
              (do-something v)
              (recur))
            ; If using pub/sub, need to unsubscribe:
            (unsub pub :foo chan)))))

    om/IWillUnmount
    (will-unmount [_]
      (put! (om/get-state owner :kill) :quit))

    om/IRenderState
    (render-state [_ {:keys [chan]}]
      (dom/div {:onClick #(put! chan [:foo "Hello"])}
        "Click me to send [:foo \"Hello\"]"))))
            


Dhruv Bhatia

unread,
Aug 30, 2014, 9:38:48 PM8/30/14
to clojur...@googlegroups.com
Thanks a lot.

I managed to create a solution which doesn't require a separate kill-channel. It seems to be side-effect free and cleans up as expected, though I’m still quite new to Clojurescript and core.async so I may be wrong!

; UTIL.CLJS
; PUB/SUB SETUP

; create a global events-ch channel which is used to transport messages
(def events-ch (chan))

; broadcaster allows us to subscribe to certain topics within events-ch
(def broadcaster
  (pub events-ch #(:topic %)))

; MY-COMPONENT.CLJS

; my-component wants to subscribe to the :login-error topic in our global events-ch channel
(defn my-component [app owner]
  (reify
    om/IInitState
    (init-state [_]
      {:subscriber-ch nil})
    om/IDidMount
    (did-mount [_]
               (let [broadcaster util/broadcaster
                     subscriber-ch (chan)]
                 (do
                   (om/set-state! owner :subscriber-ch subscriber-ch)
                   (sub broadcaster :login-error subscriber-ch)
                   (go-loop []
                            (when-let [message (:message (<! subscriber-ch))]
                              (do-something message)
                              (recur))))))
    om/IWillUnmount
    (will-unmount [_]
                  (let [broadcaster util/broadcaster
                        subscriber-ch (om/get-state owner :subscriber-ch)]
                    (do
                      (js/console.log "unsubing/closing channel" subscriber-ch)
                      (unsub broadcaster :login-error subscriber-ch)
                      (close! subscriber))))
    om/IRenderState
    (render-state [this state]
            ...



You received this message because you are subscribed to a topic in the Google Groups "ClojureScript" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojurescript/_a5dPeElqG4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojurescrip...@googlegroups.com.

Daniel Kersten

unread,
Aug 31, 2014, 6:49:24 AM8/31/14
to clojur...@googlegroups.com
I assume that (<! subscriber-ch)will return nil when subscriber-ch is closed? I vaguely remember this to be the case, though have not tested it. If it does, then your way looks good to me.

Dhruv Bhatia

unread,
Sep 1, 2014, 6:39:59 AM9/1/14
to clojur...@googlegroups.com
Thanks for reviewing my solution Daniel. I can confirm that cljs.core.async.impl.channels.ManyToManyChannel.closed gets set to true after my component is unmounted and (<! subscriber-ch)subsequently returns nil, so it looks to be working as expected!

Cheers,
Dhruv Bhatia

Daniel Kersten

unread,
Sep 1, 2014, 1:14:11 PM9/1/14
to clojur...@googlegroups.com
Perfect! Closing the channel is simpler than using a kill channel (in the cases when its possible to do this - if something else owns the channel then you can't use this method of course).


Dylan Butman

unread,
Sep 1, 2014, 2:50:56 PM9/1/14
to clojur...@googlegroups.com
I use a mixin for this purpose all the time!

(defmixin go-loop-aware
(init-aware [owner]
{:chans {:mounted (async/chan)}})
(will-unmount [owner]
(async/close! (om/get-state owner [:chans :mounted])))
(go-loop-aware [owner read-chan callback]
(when (exists? js/window)
(let [mounted (om/get-state owner [:chans :mounted])]
(go-loop []
(when-some [v (first (async/alts! [read-chan mounted]))]
(callback v)
(recur)))))))

I like the kill channel solution better personally since like Daniel point out, it doesn't require the read-chan to be owned or controlled by the component.

When [:chans :mounted] is closed, the go-loop exits. I always use when-some over when-let here since when-let would close the go block if false was ever submitted to your read channel, whereas when-some only evaluates (not= nil)

Jordan Arentsen

unread,
Nov 15, 2014, 7:53:20 PM11/15/14
to clojur...@googlegroups.com
This was so extremely helpful, thanks!
Reply all
Reply to author
Forward
0 new messages