diff --git a/crates/notebook/src/task.rs b/crates/notebook/src/task.rs index df2ac9396dd820c2fa73cfe9e332e139c2090771..add6487ca0fa71ab48de3100682528584d5c993b 100644 --- a/crates/notebook/src/task.rs +++ b/crates/notebook/src/task.rs @@ -151,7 +151,6 @@ mod web { timeout: Duration, ) -> Result { let deadline = web_time::Instant::now() + timeout; - let mut armed = false; let mut answer = std::pin::pin!(receive(receiver)); std::future::poll_fn(|context| { if let Poll::Ready(answer) = answer.as_mut().poll(context) { @@ -162,9 +161,10 @@ mod web { if web_time::Instant::now() >= deadline { return Poll::Ready(Err(std::sync::mpsc::RecvTimeoutError::Timeout)); } - if !std::mem::replace(&mut armed, true) { - after(timeout, context.waker().clone()); - } + after( + deadline.saturating_duration_since(web_time::Instant::now()), + context.waker().clone(), + ); Poll::Pending }) .await @@ -227,12 +227,11 @@ mod web { return waker.wake(); }; let wake = Closure::once_into_js(move || waker.wake()); - let _ = set_timeout.call2(&global, &wake, &(delay.as_secs_f64() * 1e3).into()); + let _ = set_timeout.call2(&global, &wake, &(delay.as_secs_f64() * 1e3).ceil().into()); } pub(crate) async fn wait(receiver: &Receiver<()>, timeout: Option) { let deadline = timeout.map(|timeout| web_time::Instant::now() + timeout); - let mut armed = false; std::future::poll_fn(|context| { let Ok(mut bell) = receiver.0.lock() else { return Poll::Ready(()); @@ -243,9 +242,7 @@ mod web { return Poll::Ready(()); } bell.waiting = Some(context.waker().clone()); - if let Some(deadline) = deadline - && !std::mem::replace(&mut armed, true) - { + if let Some(deadline) = deadline { after( deadline.saturating_duration_since(web_time::Instant::now()), context.waker().clone(), diff --git a/tools/web/receipt_probe.mjs b/tools/web/receipt_probe.mjs index 5fc7b43f1186a27220944792f80246385f843d07..7670a358025a190ea45b320cd9d88bfb328ec55c 100644 --- a/tools/web/receipt_probe.mjs +++ b/tools/web/receipt_probe.mjs @@ -7,7 +7,14 @@ import { setTimeout } from 'node:timers'; globalThis.crypto ??= webcrypto; let failure = 0; let phase = 'initializing wasm'; -globalThis.setTimeout = (callback, delay, ...args) => setTimeout(callback, delay, ...args).unref(); +let early = true; +globalThis.setTimeout = (callback, delay, ...args) => { + if (early && failure === 2 && delay <= 1000) { + early = false; + delay = 0; + } + return setTimeout(callback, delay, ...args).unref(); +}; const failures = [0, 0, 0]; globalThis.snowboundReceiptPhase = value => { phase = value; }; globalThis.snowboundReceiptFailure = value => { failure = value; }; @@ -26,7 +33,8 @@ try { await wasm.receipt_probe(); assert.ok(failures[1] >= 2, 'The active and dropped owner each retry synchronous storage failure'); assert.ok(failures[2] >= 1, 'An asynchronous storage rejection also keeps the receipt pending'); - console.log(`Actual wasm receipts passed: ${failures[1]} throws, ${failures[2]} rejections, one publication, responsive reads, retained ownership`); + assert.equal(early, false, 'A retry timer fired before its deadline'); + console.log(`Actual wasm receipts passed: ${failures[1]} throws, ${failures[2]} rejections, early timer, one publication, responsive reads, retained ownership`); } finally { clearTimeout(timeout); }