How to implement expiration timeout for HTML5

There are some crates around for using JavaScript timers, but I want a cross-platform way that uses wasm_bindgen for the browser (plus js_sys, web_sys and wasm_bindgen_futures and proper animation intervals) and fits with tokio, so that I have a solid abstraction layer over both. Anyway, I think this is trouble:

(Like tokio::time::timeout, but for the browser.)

rialight_util::timing internal submodule: platform::browser_runtime

pub async fn timeout<F: Future + Send + 'static>(duration: Duration, future: F) -> Result<(), super::ElapsedError> {
    let mut completed = Arc::new(RwLock::new(false));
    super::exec_future({
        let completed = Arc::clone(&mut completed);
        async move {
            future.await;
            *completed.write().unwrap() = true;
        }
    });
    if *completed.read().unwrap() {
        return Ok(());
    }
    todo!();
    wait(duration).await;
    Err(super::ElapsedError)
}

Any recommendation for what to do?

Full source:

The wait is implemented for instance, but it's based on a promise and I'm not sure how to poll properly before expiration

I got this idea:

#[derive(Debug)]
struct Timeout<F: Future + Send + 'static> {
    pub operation: wasm_bindgen_futures::JsFuture,
    pub resolving_to: F,
    pub expired: Arc<RwLock<bool>>,
}

impl<F: Future + Send + 'static> Future for Timeout<F> {
    type Output = Result<(), super::ElapsedError>;
    fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
        if *self.expired.read().unwrap() {
            std::pin::pin!(self.resolving_to).poll()
        } else {
            std::pin::pin!(self.resolving_to).poll()
        }
    }
}

Any idea on how to poll with a value?


This doesn't make sense :sweat_smile: I'd like to race by the way...

I'm getting this strange error:

error[E0308]: mismatched types
   --> src\util\src\timing\platform_based\browser_runtime.rs:128:9
    |
127 |         (async { future.await; }),
    |          ----------------------- the expected `async` block
128 |         (async { wait(duration).await; }),
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `async` block, found a different `async` block
    |
    = note: expected `async` block `[async block@src\util\src\timing\platform_based\browser_runtime.rs:127:10: 127:33]`
               found `async` block `[async block@src\util\src\timing\platform_based\browser_runtime.rs:128:10: 128:41]`

Code:

pub async fn timeout<F: Future<Output = ()> + Send + 'static>(duration: Duration, future: F) -> Result<(), super::ElapsedError> {
    let (_, i) = future_race([
        (async { future.await; }),
        (async { wait(duration).await; }),
    ]).await;

    match i {
        0 => Ok(()),
        1 => Err(super::ElapsedError),
    }
}

You're trying to pass an array of futures, but the concrete type of every async block is a unique opaque type. You need type erasure of some sort to store multiple async blocks in an array

1 Like

I got it... The problem is that I'm using wasm_bindgen_futures::JsFuture to convert a JavaScript promise to a Rust future.

JsFuture doesn't implement Send, therefore the wait(...) call has no compatibility with the timeout future...

If there were a way to remove that Send bound from tokio::time::timeout's given future too, it might be easy to get this working.


It looks like I got no compilation error when removing Send from given future here:

pub async fn timeout<F>(duration: Duration, future: F) -> Result<(), ElapsedError>
where
    F: Future<Output = ()> + 'static,
{
    #[cfg(feature = "rialight_default_export")] {
        return match tokio::time::timeout(duration, future).await {
            Err(_) => Err(ElapsedError),
            Ok(_) => Ok(()),
        };
    }
    #[cfg(feature = "rialight_browser_export")] {
        todo!();
    }
    #[cfg(not(any(feature = "rialight_default_export", feature = "rialight_browser_export")))] {
        let _ = (duration, future);
        panic!("Incorrectly configured Rialight runtime");
    }
}

However the issue above has not been resolved yet even removing Send.


I get it though when you mean "opaque". Even removing the Send bounds, these futures are still of separate type, right? Hmm