diff --git a/crates/test-programs/src/bin/p2_clocks_zero_wait.rs b/crates/test-programs/src/bin/p2_clocks_zero_wait.rs new file mode 100644 index 000000000000..b3f0aeab991b --- /dev/null +++ b/crates/test-programs/src/bin/p2_clocks_zero_wait.rs @@ -0,0 +1,11 @@ +fn main() { + let pollable = wasip2::clocks::monotonic_clock::subscribe_duration(0); + for _ in 0..20 { + if pollable.ready() { + return; + } + wasip2::clocks::monotonic_clock::subscribe_duration(0).block(); + } + + panic!("pollable should eventually be ready"); +} diff --git a/crates/test-programs/src/bin/p2_tcp_busy_poll.rs b/crates/test-programs/src/bin/p2_tcp_busy_poll.rs index 9f59886586fe..2a0f834c7c83 100644 --- a/crates/test-programs/src/bin/p2_tcp_busy_poll.rs +++ b/crates/test-programs/src/bin/p2_tcp_busy_poll.rs @@ -11,8 +11,6 @@ use test_programs::wasi::sockets::tcp::TcpSocket; // prevent e.g. socket readiness from being delivered. Here we verify that such // starvation does not happen. fn test_tcp_busy_poll(family: IpAddressFamily, address: IpSocketAddress) { - let zero_wait = monotonic_clock::subscribe_duration(0); - let net = Network::default(); let listener = TcpSocket::new(family).unwrap(); @@ -33,6 +31,7 @@ fn test_tcp_busy_poll(family: IpAddressFamily, address: IpSocketAddress) { let rx_ready = rx.subscribe(); let mut counter = 0; loop { + let zero_wait = monotonic_clock::subscribe_duration(0); if counter > 1_000_000 { panic!("socket still not ready!"); } diff --git a/crates/wasi/src/p2/host/clocks.rs b/crates/wasi/src/p2/host/clocks.rs index 3427c9cfd3e5..cb94d4536485 100644 --- a/crates/wasi/src/p2/host/clocks.rs +++ b/crates/wasi/src/p2/host/clocks.rs @@ -73,7 +73,7 @@ fn subscribe_to_duration( duration: tokio::time::Duration, ) -> wasmtime::Result> { let sleep = if duration.is_zero() { - table.push(Deadline::Past)? + table.push(Deadline::Past { yielded: false })? } else if let Some(deadline) = tokio::time::Instant::now().checked_add(duration) { // NB: this resource created here is not actually exposed to wasm, it's // only an internal implementation detail used to match the signature @@ -115,7 +115,7 @@ impl monotonic_clock::Host for WasiClocksCtxView<'_> { } enum Deadline { - Past, + Past { yielded: bool }, Instant(tokio::time::Instant), Never, } @@ -124,7 +124,8 @@ enum Deadline { impl Pollable for Deadline { async fn ready(&mut self) { match self { - Deadline::Past => { + Deadline::Past { yielded: true } => {} + Deadline::Past { yielded } => { // It is important we yield to Tokio here; otherwise we risk // starving `mio` such that it is unable to signal readiness for // other pollables (e.g. TCP sockets) when the guest is polling @@ -142,6 +143,7 @@ impl Pollable for Deadline { // are hypothetically other ways to generate a pollable that's // always immediately ready, which this hack doesn't cover, but // we consider this sufficient for now. + *yielded = true; tokio::task::yield_now().await } Deadline::Instant(instant) => tokio::time::sleep_until(*instant).await, diff --git a/crates/wasi/tests/all/p2/async_.rs b/crates/wasi/tests/all/p2/async_.rs index d8440cb8cdfa..9d1699f56c46 100644 --- a/crates/wasi/tests/all/p2/async_.rs +++ b/crates/wasi/tests/all/p2/async_.rs @@ -435,3 +435,8 @@ async fn file_truncation_readonly(component_path: &str) { let contents = std::fs::read(&file).expect("read truncation test file"); assert_eq!(EXPECTED_CONTENTS, contents); } + +#[test_log::test(tokio::test(flavor = "multi_thread"))] +async fn p2_clocks_zero_wait() { + run(P2_CLOCKS_ZERO_WAIT_COMPONENT, |_| {}).await.unwrap() +} diff --git a/crates/wasi/tests/all/p2/sync.rs b/crates/wasi/tests/all/p2/sync.rs index e3b54159ef88..ada68d478e51 100644 --- a/crates/wasi/tests/all/p2/sync.rs +++ b/crates/wasi/tests/all/p2/sync.rs @@ -403,3 +403,8 @@ fn file_truncation_readonly(component_path: &str) { let contents = std::fs::read(&file).expect("read truncation test file"); assert_eq!(EXPECTED_CONTENTS, contents); } + +#[test_log::test] +fn p2_clocks_zero_wait() { + run(P2_CLOCKS_ZERO_WAIT_COMPONENT, |_| {}).unwrap() +}