Fork me on GitHub
Jakub Holý (HolyJak)10:09:54

Hi! Is there a way to write a function that reads from a channel and works both when invoked from within a go block and when not? Now I have

(defn my-async-fn [tracking-channel message]
  (if (clojure.core.async.impl.dispatch/in-dispatch-thread?)
    (async/go (when-not (async/>! tracking-channel message)
                (log/debug "Failed to send")))
      (when-not (async/>!! tracking-channel message)
          (log/debug "Failed to send")))))
but it obviously relies on an impl. detail 😞

Ben Sless10:09:33

It's a bit risky to have uncontrollable puts on channels. You can theoretically start throwing exceptions

Jakub Holý (HolyJak)10:09:02

sorry, what do you mean by uncontrollable puts?


You could have only this part

(async/go (when-not (async/>! tracking-channel message)
                (log/debug "Failed to send")))
that returns a channel, and then if it’s inside a go-block read result with <!, or outside a go with <!!

Ben Sless11:09:09

this is still fragile. Imagine the tracking channel is full but you still call your function, what happens?

Ben Sless11:09:39

The problem is if you call the function over 1024 you subscribe over 1024 pending puts on the channel and start getting exceptions

Ben Sless11:09:22

you have to provide some back pressure mechanism. One way to do it is to always return a go block and park on it

Ben Sless11:09:54

This is where core.async leaks

Jan K11:09:15

In the example you can use put! instead


@UK0810AQ2 > One way to do it is to always return a go block and park on it yes that’s what I meant, so you can control if you park/block depending from where you are calling the fn


So the fn doesn’t need to know if it’s inside go or not, and it shouldn’t imo, that’s what channels are for, to communicate threads and go processes

Ben Sless12:09:17

Well, you shouldn't use >!! inside a go-block, but if you push or pull from a channel inside a spawned logical process (i.e. go block or thread) you must provide some back pressure mechanism

Ben Sless12:09:40

i.e. wait on that process


Correct, I meant only the first branch of the if should be the whole fn, so the go block ch is the return of the fn


I think it depends on the context, but why not directly putting message in tracking ch on whenever you need to, either with >! or >!!?

Jakub Holý (HolyJak)17:09:58

Thanks a lot for the input! I need to process it 🙂 It is a while since I worked with Async...


I believe the answer to your question is “no”, as far as I understand it.


… depending on the result you want to achieve.


Long story short, you always should have some policy/strategy what happens when there’s “too much stuff” inside your system and the system can’t keep up. For example, offer! will only return true if the offered value is successfully put onto the channel. When it does not return true , you need to decide what to do: 1. Forget about the value? 2. Try again? 3. “Store” the value elsewhere, and try to process later? 4 Etc.


It all depends on the use case. Some things are OK to be best effort delivery (say, performance metrics); E-commerce transactions, less so.

☝️ 2

Re: your question. Spawning an extra (go …) block just to write to a channel is not a good idea. It will not bring any benefit. Generally, outside of (go …) blocks use (put! …) and/or (offer! …) and use a backpressure strategy that works for you.


And btw, just noticed: Your question says “read from a channel” but from what I can tell, your function as written puts values onto the channel (I guess that would be a “write”? 🙂 ) In core.async lingo, the phrases “take values from a channel” and “put values onto a channel” are used more often to eliminate confusion. If you haven’t watched Rich Hickey’s talks on core.async, I highly recommend them.

Jakub Holý (HolyJak)10:09:23

Thank you! And yes, "put" would be right.

👍 2