Skip to content

Commit 83371d9

Browse files
ignatzalexcrichton
andauthored
Suggesting v45.0.1 cherry-pick for zero-delay timers (#13559)
* wasip2: Fix zero-wait pollables (#13511) * wasip2: Fix zero-wait pollables This commit fixes an accidental bug introduced in #13085 where repeatedly calling `ready()` on a zero-wait pollable in WASIp2 would never resolve. In #13085 zero-length waits were updated to unconditionally yield to tokio to improve fairness, but this didn't take into account where the yield was repeatedly cancelled and never completed. The fix in this commit is to attempt the yield once and then never attempt it again. If the original yield is cancelled the next check on the pollable will go through. This is sort of a cancellation-safety fix where the previous implementation wasn't necessarily cancellation safe in the sense that repeatedly checking-and-cancelling never let anything progress, which is counterintuitive. Closes #13507 * Fix compile * Tweak tcp_busy_poll test * Add a v45.0.1 entry to RELEASE.md. --------- Co-authored-by: Alex Crichton <alex@alexcrichton.com>
1 parent 377cd91 commit 83371d9

6 files changed

Lines changed: 39 additions & 5 deletions

File tree

RELEASES.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
## 45.0.1
2+
3+
Released 2026-06-05.
4+
5+
### Fixed
6+
7+
* Fixed regression with WASIp2 zero-delay clocks/timers: allow repeat calls to
8+
`.ready()` to make progress.
9+
[#13511](https://github.com/bytecodealliance/wasmtime/pull/13511)
10+
11+
--------------------------------------------------------------------------------
12+
113
## 45.0.0
214

315
Released 2026-05-21.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fn main() {
2+
let pollable = wasip2::clocks::monotonic_clock::subscribe_duration(0);
3+
for _ in 0..20 {
4+
if pollable.ready() {
5+
return;
6+
}
7+
wasip2::clocks::monotonic_clock::subscribe_duration(0).block();
8+
}
9+
10+
panic!("pollable should eventually be ready");
11+
}

crates/test-programs/src/bin/p2_tcp_busy_poll.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ use test_programs::wasi::sockets::tcp::TcpSocket;
1111
// prevent e.g. socket readiness from being delivered. Here we verify that such
1212
// starvation does not happen.
1313
fn test_tcp_busy_poll(family: IpAddressFamily, address: IpSocketAddress) {
14-
let zero_wait = monotonic_clock::subscribe_duration(0);
15-
1614
let net = Network::default();
1715

1816
let listener = TcpSocket::new(family).unwrap();
@@ -33,6 +31,7 @@ fn test_tcp_busy_poll(family: IpAddressFamily, address: IpSocketAddress) {
3331
let rx_ready = rx.subscribe();
3432
let mut counter = 0;
3533
loop {
34+
let zero_wait = monotonic_clock::subscribe_duration(0);
3635
if counter > 1_000_000 {
3736
panic!("socket still not ready!");
3837
}

crates/wasi/src/p2/host/clocks.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fn subscribe_to_duration(
7373
duration: tokio::time::Duration,
7474
) -> wasmtime::Result<Resource<DynPollable>> {
7575
let sleep = if duration.is_zero() {
76-
table.push(Deadline::Past)?
76+
table.push(Deadline::Past { yielded: false })?
7777
} else if let Some(deadline) = tokio::time::Instant::now().checked_add(duration) {
7878
// NB: this resource created here is not actually exposed to wasm, it's
7979
// only an internal implementation detail used to match the signature
@@ -115,7 +115,7 @@ impl monotonic_clock::Host for WasiClocksCtxView<'_> {
115115
}
116116

117117
enum Deadline {
118-
Past,
118+
Past { yielded: bool },
119119
Instant(tokio::time::Instant),
120120
Never,
121121
}
@@ -124,7 +124,8 @@ enum Deadline {
124124
impl Pollable for Deadline {
125125
async fn ready(&mut self) {
126126
match self {
127-
Deadline::Past => {
127+
Deadline::Past { yielded: true } => {}
128+
Deadline::Past { yielded } => {
128129
// It is important we yield to Tokio here; otherwise we risk
129130
// starving `mio` such that it is unable to signal readiness for
130131
// other pollables (e.g. TCP sockets) when the guest is polling
@@ -142,6 +143,7 @@ impl Pollable for Deadline {
142143
// are hypothetically other ways to generate a pollable that's
143144
// always immediately ready, which this hack doesn't cover, but
144145
// we consider this sufficient for now.
146+
*yielded = true;
145147
tokio::task::yield_now().await
146148
}
147149
Deadline::Instant(instant) => tokio::time::sleep_until(*instant).await,

crates/wasi/tests/all/p2/async_.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,3 +435,8 @@ async fn file_truncation_readonly(component_path: &str) {
435435
let contents = std::fs::read(&file).expect("read truncation test file");
436436
assert_eq!(EXPECTED_CONTENTS, contents);
437437
}
438+
439+
#[test_log::test(tokio::test(flavor = "multi_thread"))]
440+
async fn p2_clocks_zero_wait() {
441+
run(P2_CLOCKS_ZERO_WAIT_COMPONENT, |_| {}).await.unwrap()
442+
}

crates/wasi/tests/all/p2/sync.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,8 @@ fn file_truncation_readonly(component_path: &str) {
403403
let contents = std::fs::read(&file).expect("read truncation test file");
404404
assert_eq!(EXPECTED_CONTENTS, contents);
405405
}
406+
407+
#[test_log::test]
408+
fn p2_clocks_zero_wait() {
409+
run(P2_CLOCKS_ZERO_WAIT_COMPONENT, |_| {}).unwrap()
410+
}

0 commit comments

Comments
 (0)