core-async

Steven Katz 2021-04-15T18:15:55.088100Z

I have a basic channel closing question. If I am the creater of a channel (and the only writer) should I close the channel after putting on its one and only value? The use case is connecting the call back from http-kit into core async/channels. My original thought is:

(defn call->channel [url]
  (let [c (chan)]
    (http/get url (fn [{:keys [status headers body error] :as resp}]
                                        (if error
                                          (put! c error)
                                          (put! c resp))))
    c)) 

Steven Katz 2021-04-15T18:18:22.088600Z

But should it be:

(defn call->channel [url]
  (let [c (chan)]
    (http/get url (fn [{:keys [status headers body error] :as resp}]
                                        (if error
                                          (put! c error)
                                          (put! c resp))
                    (close! c)))
    c))

ghadi 2021-04-15T18:20:40.089700Z

the second one is incorrect because the channel is unbuffered

ghadi 2021-04-15T18:21:09.090300Z

unbuffered (aka "rendezvous") channels, require the putter and taker to rendezvous

ghadi 2021-04-15T18:21:48.091100Z

when you put! but no one there to rendezvous with take, then close, the put will never complete into the channel

ghadi 2021-04-15T18:22:11.091700Z

so either 1) don't close, or 2) close && buffer the chan (chan 1)

phronmophobic 2021-04-15T18:22:50.092600Z

if you only intend on putting one value, then you may want to use https://clojuredocs.org/clojure.core.async/promise-chan

Steven Katz 2021-04-15T18:22:55.092800Z

ok, but in case 1 who would be responsible for closing, the reader?

ghadi 2021-04-15T18:24:09.093100Z

no, reader closing is often a bad pattern

ghadi 2021-04-15T18:24:18.093400Z

you could choose not to close

ghadi 2021-04-15T18:24:32.093900Z

I think your best bet is buffer=1 + close

Steven Katz 2021-04-15T18:24:36.094100Z

so no one closes it, maybe I’m miss-understanding the need to close channels at all

ghadi 2021-04-15T18:24:58.094800Z

thread does something similar to your usecase internally

Steven Katz 2021-04-15T18:25:35.095200Z

still have the issue of closing the channel though?

Steven Katz 2021-04-15T18:32:37.097300Z

Thanks, I’ll take a look. In general is it more typical to have channels with a buffer >= 1 when the channel is going to be consumed in a go block? (and I am correct in thinking that put! does not park/block the caller?)

ghadi 2021-04-15T18:34:56.097700Z

in general you have to think carefully about the buffers

ghadi 2021-04-15T18:35:13.098200Z

the same algorithm might be correct without buffers, then deadlock with buffering

ghadi 2021-04-15T18:35:48.098900Z

in this case, you know there will be one thing put into the channel, and that it might need to be put into the channel before the consumer takes it

ghadi 2021-04-15T18:36:31.099400Z

buffering is not required, and there are various strategies (dropping/sliding buffers)

ghadi 2021-04-15T18:36:53.100100Z

rendezvous channels (no buffer) are very useful and are essentially the core primitive in the CSP paradigm

ghadi 2021-04-15T18:39:20.100500Z

a lot of the pictures from the old JCSP project are relevant to core.async

ghadi 2021-04-15T18:39:36.100900Z

https://www.cs.kent.ac.uk/projects/ofa/jcsp/

Steven Katz 2021-04-15T18:45:03.102600Z

This is from the doc on close:

Logically closing happens after all puts have been delivered. Therefore, any
blocked or parked puts will remain blocked/parked until a taker releases them.
This implies to me that puts on an unbuffered channel would be allowed to be taken even if it has been closed?

Steven Katz 2021-04-15T18:46:00.103100Z

(going to write some test code for my own understanding anyway)

phronmophobic 2021-04-15T18:58:55.104600Z

Is there a requirement that the chan must be closed? For most use cases, simply putting a single value on the promise chan is all that is required.

Steven Katz 2021-04-15T19:02:12.107Z

testing seems to indicate that this worked:

(defn blah []
  (let [c (chan)]
    (go (>! c "hello")
        (close! c))
    c))

atom.core> (def c (blah))
#'atom.core/c
atom.core> (<!! c)
"hello"
atom.core> (<!! c)
nil
So putting a value on an unbuffered channel and then closing it still allows the value to be delivered and then the channel is closed. I think the code snippet I first posted might have been misleading as it make the close look like it is part of the outer function and not the callback.

ghadi 2021-04-15T19:03:58.107400Z

you are misinterpreting

ghadi 2021-04-15T19:04:20.107900Z

it's the first <!! that is allowing the >! to proceed

ghadi 2021-04-15T19:04:29.108200Z

the channel is closed after the >!

ghadi 2021-04-15T19:07:37.109400Z

the docstring on close! is related to put that are "delivered" -- delivered means you're putting into a channel with an open space in a buffer or a put with a rendezvous take

Steven Katz 2021-04-15T19:32:38.115300Z

Here’s whats in my head: Without any buffer: 1. Try to put something on the channel in a go block, get parked 2. Someone tries to take, it works because their is a pending put 3. execution in the go block resumes because of the take, channel gets closed With buffer == 1 1. Try to put something on the channel in a go block, succeeds because buffer is empty 2. execution continues, channel is closed 3. Someone tries to take, it works because there is a value in the buffer Is this interpretation correct?

raspasov 2021-04-15T19:34:09.116500Z

@steven.katz why do you need to close the channel? If you can avoid closing, that usually makes things simpler.

Steven Katz 2021-04-15T19:34:51.118100Z

Thats part of my original question, “Should I care about closing channels the same way I care about closing files etc?” (maybe its a language thing)

raspasov 2021-04-15T19:35:02.118400Z

I don’t think so

raspasov 2021-04-15T19:35:26.119300Z

A promise chan that receives a value is effectively “closed”, since it will keep returning the same value and no other value can replace it

raspasov 2021-04-15T19:38:09.122700Z

Don’t close channels unless you can’t solve your problem without closing. If there are no more references to that channel, it will be garbage collected (barring any gotchas and bugs). Closing shouldn’t make a difference.

1✅
cassiel 2021-04-16T09:21:35.125400Z

Sorry for jumping in at the end of a conversation but: in the Java/Clojure world with real multithreading, if I let a non-closed channel, with a thread waiting to read from it, drop out of scope, is everything GCed? Or are threads treated as roots for GC?

cassiel 2021-04-16T18:54:29.128700Z

Way back when I was a wee nipper, we learned that the GC roots included any threads, since they could still be active even if nothing they were doing was still reachable from the main thread’s environment and stack. But my knowledge here is old.