bytecodealliance / javy

JS to WebAssembly toolchain
Apache License 2.0
2.2k stars 106 forks source link

Implement Cancellable or interruptible setTimeout in javy #650

Open redoC-A2k opened 4 months ago

redoC-A2k commented 4 months ago

How can I implement Implement Cancellable or interruptible timeout in javy ? I am right now polyfilling setTimeout like below -

fn set_timeout_api() -> impl FnMut(&JSContextRef, JSValueRef, &[JSValueRef]) -> Result<JSValue> {
    move |context: &JSContextRef, _this: JSValueRef, args: &[JSValueRef]| {
        let callback = args.get(0).unwrap();
        let default = to_qjs_value(context, &JSValue::Int(0)).unwrap();
        let timeout = args.get(1).unwrap_or(&default);
        thread::sleep(Duration::from_millis(
            timeout
                .as_f64()
                .expect("Unable to convert timeout to milliseconds") as u64,
        ));
        println!("timeout reached");
        if callback.is_function() {
            let mut argsvec: Vec<JSValueRef> = vec![];
            if args.len() > 2 {
                for i in 2..args.len() {
                    argsvec.push(args.get(i).unwrap().to_owned())
                }
            }
            let res = callback.call(
                &to_qjs_value(context.to_owned(), &JSValue::Undefined).unwrap(),
                &argsvec,
            );
        }
        Ok(JSValue::Undefined)
    }
}

But it is not cancellable it will only be cleared after timeout .

redoC-A2k commented 4 months ago

Please could you guys help ?

saulecabrera commented 4 months ago

According to the setTimeout API, order to make the timeout cancellable, you'd need to use the clearTimeout API. So, roughly, you'd need to return an id from your implementation that can be later passed to clearTimeout. Have you tried this approach? Or is it something different what you're trying to achieve?

Regarding the snippet above, it's also worth noting that there's still work to be done when it comes to Wasm atomics/threads.

jeffcharles commented 4 months ago

You may want to consider using a different implementation than thread::sleep. thread::sleep will block the JS event queue which will make canceling it from within JS impossible. You could have something on the host side cancel it.

redoC-A2k commented 4 months ago

@jeffcharles I was considering exposing a async function from host to guest ... but again wrap_callback takes a synchronous closure as argument , so I won't be able to call that async function asynchronously from guest wasm in setTimeout polyfill method . Could you please tell me more what I should do ? Or suggest me some alternatives .... I am fine with any implementation for polyfilling setTimeout , all I want is it to be cancellable .

jeffcharles commented 4 months ago

You could take a look at how QuickJS implemented setTimeout in https://github.com/bellard/quickjs/blob/d378a9f3a583cb787c390456e27276d0ee377d23/quickjs-libc.c#L2034. You can see they have a recurring check for each timer in a poll function that's run inside of a loop to see if any timers have expired in https://github.com/bellard/quickjs/blob/d378a9f3a583cb787c390456e27276d0ee377d23/quickjs-libc.c#L2326.

We've also switched off of quickjs-wasm-rs and on to rquickjs in #618 for Javy. So it may be worth investigating if there's an API in rquickjs like Ctx.spawn that you can use.