Skip to content

Commit 8ded8f9

Browse files
authored
Add some more async tests (#1287)
* Add some more async tests I went through various parts of the guest runtime and sprinkled `if true { panic!() }` throughout. Anything that wasn't hit got a test. This led to a few issues in wasip3-prototyping: * bytecodealliance/wasip3-prototyping#137 * bytecodealliance/wasip3-prototyping#138 * bytecodealliance/wasip3-prototyping#139 * bytecodealliance/wasip3-prototyping#140 so not all tests are fully enabled yet but they should get enabled as bugs get fixed. Additionally this starts to use `CALLBACK_CODE_YIELD`. That's disabled for callbacks due to bytecodealliance/wasip3-prototyping#140 but it's enabled for `block_on` where it translates to a `waitable-set.poll` instead of a `waitable-set.wait`. * Fix fallback build of `poll` * Adjust poll signature * Fix native signature
1 parent 8656e20 commit 8ded8f9

File tree

18 files changed

+397
-14
lines changed

18 files changed

+397
-14
lines changed

crates/guest-rust/rt/src/async_support.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,7 @@ impl FutureState {
129129
self.deliver_waitable_event(event1, event2)
130130
}
131131

132-
loop {
133-
match self.poll() {
134-
// TODO: don't re-loop here once a host supports
135-
// `CALLBACK_CODE_YIELD`.
136-
CALLBACK_CODE_YIELD => {}
137-
other => break other,
138-
}
139-
}
132+
self.poll()
140133
}
141134

142135
/// Deliver the `code` event to the `waitable` store within our map. This
@@ -358,12 +351,24 @@ pub unsafe fn callback(event0: u32, event1: u32, event2: u32) -> u32 {
358351
// our future so deallocate it. Otherwise put our future back in
359352
// context-local storage and forward the code.
360353
unsafe {
361-
let rc = (*state).callback(event0, event1, event2);
354+
let rc = match (*state).callback(event0, event1, event2) {
355+
// FIXME(wasip3-prototyping#140) this seems to break tests in
356+
// that repo. Handle this return code by re-running our callback
357+
// until it stops yielding.
358+
CALLBACK_CODE_YIELD => loop {
359+
match (*state).callback(EVENT_NONE, 0, 0) {
360+
CALLBACK_CODE_YIELD => {}
361+
other => break other,
362+
}
363+
},
364+
other => other,
365+
};
362366
if rc == CALLBACK_CODE_EXIT {
363367
drop(Box::from_raw(state));
364368
} else {
365369
context_set(state.cast());
366370
}
371+
rtdebug!(" => (cb) {rc:#x}");
367372
rc
368373
}
369374
}
@@ -389,6 +394,7 @@ pub fn block_on<T: 'static>(future: impl Future<Output = T> + 'static) -> T {
389394
loop {
390395
match state.callback(event.0, event.1, event.2) {
391396
CALLBACK_CODE_EXIT => break rx.try_recv().unwrap().unwrap(),
397+
CALLBACK_CODE_YIELD => event = state.waitable_set.as_ref().unwrap().poll(),
392398
_ => event = state.waitable_set.as_ref().unwrap().wait(),
393399
}
394400
}

crates/guest-rust/rt/src/async_support/future_support.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,7 @@ impl<T> fmt::Display for FutureWriteError<T> {
437437
impl<T> std::error::Error for FutureWriteError<T> {}
438438

439439
/// Result of [`FutureWrite::cancel`].
440+
#[derive(Debug)]
440441
pub enum FutureWriteCancel<T: 'static> {
441442
/// The cancel request raced with the receipt of the sent value, and the
442443
/// value was actually sent. Neither the value nor the writer are made

crates/guest-rust/rt/src/async_support/subtask.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ unsafe impl<T: Subtask> WaitableOp for SubtaskOps<T> {
149149
trap_because_of_future_cancel()
150150
}
151151

152-
fn result_into_cancel(_result: Self::Result) -> Self::Cancel {
153-
todo!()
152+
fn result_into_cancel(result: Self::Result) -> Self::Cancel {
153+
drop(result);
154154
}
155155
}
156156

crates/guest-rust/rt/src/async_support/waitable.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,15 @@ where
322322
if let Some(cx) = cx {
323323
let handle = S::in_progress_waitable(in_progress);
324324
self.register_waker(handle, cx);
325+
} else {
326+
// This should not be dynamically reachable and, if it were, it may
327+
// mean that this needs to be re-thought and/or the caller should be
328+
// adjusted. Conservatively panic for now to defer fleshing this out
329+
// for later.
330+
panic!(
331+
"unexpected poll to completion in a non-future way (no context) \
332+
and this operation is still pending"
333+
);
325334
}
326335
Poll::Pending
327336
}

crates/guest-rust/rt/src/async_support/waitable_set.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,51 @@
11
//! Low-level FFI-like bindings around `waitable-set` in the canonical ABI.
22
3+
use super::EVENT_NONE;
34
use std::num::NonZeroU32;
45

56
pub struct WaitableSet(NonZeroU32);
67

78
impl WaitableSet {
89
pub fn new() -> WaitableSet {
9-
WaitableSet(NonZeroU32::new(unsafe { new() }).unwrap())
10+
let ret = WaitableSet(NonZeroU32::new(unsafe { new() }).unwrap());
11+
rtdebug!("waitable-set.new() = {}", ret.0.get());
12+
ret
1013
}
1114

1215
pub fn join(&self, waitable: u32) {
16+
rtdebug!("waitable-set.join({waitable}, {})", self.0.get());
1317
unsafe { join(waitable, self.0.get()) }
1418
}
1519

1620
pub fn remove_waitable_from_all_sets(waitable: u32) {
21+
rtdebug!("waitable-set.join({waitable}, 0)");
1722
unsafe { join(waitable, 0) }
1823
}
1924

2025
pub fn wait(&self) -> (u32, u32, u32) {
2126
unsafe {
2227
let mut payload = [0; 2];
2328
let event0 = wait(self.0.get(), &mut payload);
29+
rtdebug!(
30+
"waitable-set.wait({}) = ({event0}, {:#x}, {:#x})",
31+
self.0.get(),
32+
payload[0],
33+
payload[1],
34+
);
35+
(event0, payload[0], payload[1])
36+
}
37+
}
38+
39+
pub fn poll(&self) -> (u32, u32, u32) {
40+
unsafe {
41+
let mut payload = [0; 2];
42+
let event0 = poll(self.0.get(), &mut payload);
43+
rtdebug!(
44+
"waitable-set.poll({}) = ({event0}, {:#x}, {:#x})",
45+
self.0.get(),
46+
payload[0],
47+
payload[1],
48+
);
2449
(event0, payload[0], payload[1])
2550
}
2651
}
@@ -33,6 +58,7 @@ impl WaitableSet {
3358
impl Drop for WaitableSet {
3459
fn drop(&mut self) {
3560
unsafe {
61+
rtdebug!("waitable-set.drop({})", self.0.get());
3662
drop(self.0.get());
3763
}
3864
}
@@ -54,6 +80,10 @@ unsafe fn join(_: u32, _: u32) {
5480
unsafe fn wait(_: u32, _: *mut [u32; 2]) -> u32 {
5581
unreachable!();
5682
}
83+
#[cfg(not(target_arch = "wasm32"))]
84+
unsafe fn poll(_: u32, _: *mut [u32; 2]) -> u32 {
85+
unreachable!();
86+
}
5787

5888
#[cfg(target_arch = "wasm32")]
5989
#[link(wasm_import_module = "$root")]
@@ -66,4 +96,6 @@ extern "C" {
6696
fn join(waitable: u32, set: u32);
6797
#[link_name = "[waitable-set-wait]"]
6898
fn wait(_: u32, _: *mut [u32; 2]) -> u32;
99+
#[link_name = "[waitable-set-poll]"]
100+
fn poll(_: u32, _: *mut [u32; 2]) -> u32;
69101
}

crates/guest-rust/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -895,6 +895,7 @@ pub mod rt {
895895

896896
#[cfg(feature = "async")]
897897
pub use wit_bindgen_rt::async_support::{
898-
AbiBuffer, FutureRead, FutureReader, FutureWrite, FutureWriter, StreamRead, StreamReader,
899-
StreamResult, StreamWrite, StreamWriter,
898+
block_on, spawn, AbiBuffer, FutureRead, FutureReader, FutureWrite, FutureWriteCancel,
899+
FutureWriteError, FutureWriter, StreamRead, StreamReader, StreamResult, StreamWrite,
900+
StreamWriter,
900901
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
include!(env!("BINDINGS"));
2+
3+
use crate::my::test::i::*;
4+
5+
fn main() {
6+
wit_bindgen::block_on(async {
7+
let (tx, rx) = wit_future::new();
8+
cancel_before_read(rx).await;
9+
drop(tx);
10+
11+
let (tx, rx) = wit_future::new();
12+
cancel_after_read(rx).await;
13+
drop(tx);
14+
15+
start_read_then_cancel().await;
16+
});
17+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use wit_bindgen::FutureReader;
2+
3+
use futures::task::noop_waker_ref;
4+
use std::future::{Future, IntoFuture};
5+
use std::task::Context;
6+
7+
include!(env!("BINDINGS"));
8+
9+
struct Component;
10+
11+
export!(Component);
12+
13+
impl crate::exports::my::test::i::Guest for Component {
14+
async fn cancel_before_read(x: FutureReader<u32>) {
15+
let mut read = Box::pin(x.into_future());
16+
let reader = read.as_mut().cancel().unwrap_err();
17+
drop(reader);
18+
}
19+
20+
async fn cancel_after_read(x: FutureReader<u32>) {
21+
let mut read = Box::pin(x.into_future());
22+
assert!(read
23+
.as_mut()
24+
.poll(&mut Context::from_waker(noop_waker_ref()))
25+
.is_pending());
26+
let reader = read.as_mut().cancel().unwrap_err();
27+
drop(reader);
28+
}
29+
30+
async fn start_read_then_cancel() {
31+
// FIXME(wasip3-prototyping#137)
32+
if false {
33+
let (tx, rx) = wit_future::new::<u32>();
34+
let mut read = Box::pin(rx.into_future());
35+
assert!(read
36+
.as_mut()
37+
.poll(&mut Context::from_waker(noop_waker_ref()))
38+
.is_pending());
39+
drop(tx);
40+
assert!(read.as_mut().cancel().unwrap().is_none());
41+
}
42+
}
43+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package my:test;
2+
3+
interface i {
4+
cancel-before-read: async func(x: future<u32>);
5+
cancel-after-read: async func(x: future<u32>);
6+
start-read-then-cancel: async func();
7+
}
8+
9+
world test {
10+
export i;
11+
}
12+
13+
world runner {
14+
import i;
15+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
include!(env!("BINDINGS"));
2+
3+
use crate::my::test::i::{read_and_drop, take_then_close};
4+
use futures::task::noop_waker_ref;
5+
use std::future::Future;
6+
use std::task::Context;
7+
use wit_bindgen::FutureWriteCancel;
8+
9+
fn main() {
10+
wit_bindgen::block_on(async {
11+
// cancel from the other end
12+
let (tx, rx) = wit_future::new();
13+
let f1 = async { tx.write("hello".into()).await };
14+
let f2 = async { take_then_close(rx) };
15+
let (result, ()) = futures::join!(f1, f2);
16+
assert_eq!(result.unwrap_err().value, "hello");
17+
18+
// cancel before we actually hit the intrinsic
19+
let (tx, _rx) = wit_future::new::<String>();
20+
let mut future = Box::pin(tx.write("hello2".into()));
21+
let tx = match future.as_mut().cancel() {
22+
FutureWriteCancel::Cancelled(val, tx) => {
23+
assert_eq!(val, "hello2");
24+
tx
25+
}
26+
_ => unreachable!(),
27+
};
28+
29+
// cancel after we hit the intrinsic
30+
let mut future = Box::pin(tx.write("hello3".into()));
31+
assert!(future
32+
.as_mut()
33+
.poll(&mut Context::from_waker(noop_waker_ref()))
34+
.is_pending());
35+
match future.as_mut().cancel() {
36+
FutureWriteCancel::Cancelled(val, _) => {
37+
assert_eq!(val, "hello3");
38+
}
39+
_ => unreachable!(),
40+
};
41+
42+
// cancel after we hit the intrinsic and then close the other end
43+
//
44+
// FIXME(wasip3-prototyping#137)
45+
if false {
46+
let (tx, rx) = wit_future::new::<String>();
47+
let mut future = Box::pin(tx.write("hello3".into()));
48+
assert!(future
49+
.as_mut()
50+
.poll(&mut Context::from_waker(noop_waker_ref()))
51+
.is_pending());
52+
drop(rx);
53+
match future.as_mut().cancel() {
54+
FutureWriteCancel::Closed(val) => assert_eq!(val, "hello3"),
55+
other => panic!("expected closed, got: {other:?}"),
56+
};
57+
}
58+
59+
// Start a write, wait for it to be pending, then go complete the write
60+
// in some async work, then cancel it and witness that it was written,
61+
// not cancelled.
62+
//
63+
// FIXME(wasip3-prototyping#138)
64+
if false {
65+
let (tx, rx) = wit_future::new::<String>();
66+
let mut future = Box::pin(tx.write("hello3".into()));
67+
assert!(future
68+
.as_mut()
69+
.poll(&mut Context::from_waker(noop_waker_ref()))
70+
.is_pending());
71+
read_and_drop(rx).await;
72+
match future.as_mut().cancel() {
73+
FutureWriteCancel::AlreadySent => {}
74+
other => panic!("expected sent, got: {other:?}"),
75+
};
76+
}
77+
});
78+
}

0 commit comments

Comments
 (0)