(CLJS) Make (<! (timeout 0)) be closer to 0

Description

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 [f delay]
(if (= 0 delay)
(goog.async.nextTick f) ;; <---- new path for 0
(js/setTimeout f delay)) ;; <---- existing path

Environment

None

Activity

Show:

Mike Thompson November 3, 2016 at 6:13 AM

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/

Mike Thompson August 25, 2015 at 11:04 AM

That's nice! Certainly provides a placeholder solution.

import August 16, 2015 at 3:59 AM

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)) ...)

Mike Thompson August 7, 2015 at 5:06 AM

@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.

Mike Thompson August 7, 2015 at 4:32 AM

@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"

Won't Fix

Details

Assignee

Reporter

Priority

Created August 4, 2015 at 3:50 AM
Updated March 31, 2020 at 6:50 PM
Resolved March 31, 2020 at 6:50 PM