Share your thoughts in the 2024 State of Clojure Survey!

Welcome! Please see the About page for a little more info on how this works.

0 votes
in core.async by

Currently (<! (timeout 0)) will take about 4ms on a modern browser because it ends up as (js/setTimeout f 0). On a modern browser, setTimeout won't come back in under 4ms, even if given a 0. And 4ms is too long if you are just wanting to hand back control to the browser for the minimum about of time.

See:
https://github.com/clojure/core.async/blob/d073896192fa55fab992eb4c9ea57b86ec5cf076/src/main/clojure/cljs/core/async/impl/timers.cljs#L161-L164

followed by:
https://github.com/clojure/core.async/blob/d073896192fa55fab992eb4c9ea57b86ec5cf076/src/main/clojure/cljs/core/async/impl/dispatch.cljs#L35-L36

I was imagining this tiny rewrite:

(defn queue-delay (link: f delay)
(if (= 0 delay)

  (goog.async.nextTick f)      ;;  <---- new path for 0
 (js/setTimeout f delay))      ;;  <---- existing path

11 Answers

0 votes
by

Comment made by: pkobrien

I think the use of goog.Timer.callOnce should work for any amount of time and would still solve the problem posed by the use of js/setTimeout.

0 votes
by

Comment made by: mikethompson

From what I can see, goog.Timer.callOnce use setTimeout. But that's specifically what we're trying to avoid in the case of a 0ms duration. So I don't see callOnce as a solution.

0 votes
by

Comment made by: pkobrien

I think you are right, Mike. Hard as it is to believe, since obviously Google knows about the problem. But now that I'm rereading the goog.Timer code I can't see that they've fixed it here. Looks like goog.async.nextTick is the only solution.

0 votes
by

Comment made by: pkobrien

I just wanted to mention that this issue is for the ClojureScript version of core.async, and that it appears to be fairly idiomatic to use (<! (timeout 0)) as a way to temporarily pass control back to the js event processing. So I think it is very important to make this efficient.

0 votes
by

Comment made by: gshayban

In reading this "bug", it seems that the actual problem is having more varied goblock dispatch in CLJS. It could/should be improved, but making (<! (timeout 0)) -- non-sensical code -- behave differently is not addressing the problem in a meaningful way.

We should eliminate the need for this hack at the root, rather than make undefined behavior more specific. ASYNC-131 and ASYNC-43 are related and probably better starting points.

0 votes
by

Comment made by: mikethompson

@Ghadi
1. I can't see how issue 131 is closely enough related to act as a "better starting point". It has a different thrust, represents different needs, and will likely have a different solution (as evidenced by the current suggestions).

  1. It almost looks to me as if Issue 43 could be closed with the switch to Google Closure dispatch noted by David Nolen. BTW, you'll note that my suggestion is to indeed use goog.async.nextTick (so I'm adhering to the use Google Closure approach).

Also, I would disagree with your assertion that (<! (timeout 0)) is "non-sensical code". In a single threaded environment, like the browser, there is sometimes a need to explicitly "hand back control" to the browser from within go loops (if that go loop is, for a time, hogging the CPU). At the moment (<! (timeout 0)) is the only way I know to do that. It could reworked to be (<! (yield-cpu)) in order to make the intent clearer, and that might be nice, but what I care about is having a way of yielding which doesn't always cost 4ms. I believe this need is genuine, and very "sensical" :-)

0 votes
by

Comment made by: mikethompson

@Ghadi
Just to be clear ... you refer to this as a "bug" in double quotes. Almost as if it isn't a bug.

But for me this is a sufficiently real "bug" that I'll have to either work with a fork OR move away from using core.async entirely.

I need (<! (timeout 0)) to mean 0, or as close to it as possible. I need it to work as advertised. The current implementation does not deliver on 0 (or as close to it as possible), and instead delivers the same as (<! (timeout 4)) and 4ms is FOREVER compared to 0ms (or close to it). At 4ms each time around, a goloop can only iterate 250 times a second.

Anyone who has a goloop listening to the output of a bursty websocket will have this problem. They need to process like crazy (no 4ms delays) BUT also hand back control so the browser can do what it needs to, every now and again. All of this get exacerbated when a browser page loses "focus", goes into the background, and the js gets throttled, animation frames slow down, etc.

0 votes
by

Comment made by: pkobrien

In case it helps anyone who might be reading this, I have begun using the following as a temporary solution:

`
(defn yield []
(let [ch (chan)]

(goog.async.nextTick #(close! ch))
ch))

(go
; Some code...
(<! (yield)) ;; Instead of (<! (timeout 0))
...)
`

0 votes
by

Comment made by: mikethompson

That's nice! Certainly provides a placeholder solution.

0 votes
by

Comment made by: mikethompson

After further discussion with halgari, I believe this should be closed as "Won't Fix" (I can't figure out how to close).

https://www.reddit.com/r/Clojure/comments/5aprn3/in_coreasync_using_timeout_with_0_delay_causes_a/

0 votes
by
Reference: https://clojure.atlassian.net/browse/ASYNC-137 (reported by mikethompson)
...