Priority, window.requestAnimationFrame, wasm32, async Rust

  1. We have async Rust code compiled to wasm32 and running on the main/DOM thread in Chrome.

  2. This Rust code has (atleast) two things:
    2.1 a handle registered (via web_sys) for window.requestAnimaionFrame
    2.2 some async tasks running

Question: How does priority work ?

I understand that async tasks are not pre-emptive, i.e. we can't just suspend it and start running a requestAnimationFrame.

My question is: suppose
3.1 an async task does an await,
3.2 other async tasks are ready,
3.3 there is also a ready window.requestAnimationFrame

In this case, is there anyway we can force the window.requestAnimationFrame to have "higher priority" than the waiting Rust async tasks ?

Assuming you're writing the async executor yourself, you can just check whether you need to run a priority task before dequeueing a normal task

  1. I'm using wasm-bindgen-futures, not writing my own executor.

  2. window.requestAnimationFrame is a callback, not a task

If you're just calling a function from your wasm module in the JS callback that shouldn't interact with the async runtime at all.

wasm_bindgen_futures appears to use the JS runtime's scheduling, which I think generally prioritizes callbacks like that over running arbitrary promises. All of the tasks queued for the running tick will have to be polled before wasm_bindgen_futures will surrender the thread back to the JS runtime. If that's causing lag it probably means you're doing too much work in a single poll to one of the futures.

They are running on the same thread, the Document-Object-Model / Window thread.

===

This sounds like Window.requestAnimationFrame will get priority.

This sounds like the opposite -- that when one async task awaits, other Rust async tasks will be polled before giving control back to JS (and thus allowing Window.requestAnimationFrame to run).

I don't agree with this: based on the statement of your previous sentence, there is the possibility of lag caused by LOTS of async tasks, even if each one is very short, if it is true that wasm_bindgen_futures polls them all before returning control back to JS.

I'm under the impression it gets priority in the JavaScript runtime, yes

wasm_bindgen_futures coalesces multiple futures into a single JS Promise callback, which results in this slightly confusing situation. wasm_bindgen_futures doesn't yield back to the JS runtime without polling all the futures that accumulated since the last JS microtask tick.

You could fork wasm_bindgen_futures and add some way to exit early if you've done a lot of work in a single tick already, though tuning that correctly would probably be a pain.

You would need to have all of those tasks wake/spawn between microtask ticks. It's not impossible, but I don't think most codebases will have that kind of behavior.

  1. I find this counter intuitive.
  2. Now that you have explained it, the reasoning does make sense.
  3. Can you point me at the code that implements this mechanic? I am interested in how this works.

It's confusing for sure.

Most of the logic is in queue.rs but there isn't a lot of it.