Skip to content

Commit b38f4bd

Browse files
authored
generate wasmtime-wasi export bindings with task_exit option (#12185)
* generate `wasmtime-wasi` export bindings with `task_exit` option Previously, `wasmtime run` only ran `wasi:cli/[email protected]` tasks until they returned a result, then exited. That meant any post-return computation done by the task might not have a chance to run. This commit aligns `wasmtime-wasi` with what `wasmtime-wasi-http` was already doing: specifying the `task_exit` option for export bindings to `wasmtime-wit-bindgen` so the host can `.await` both the return value _and_ the task completion. It also modifies the `wasmtime run` implementation to await both. * address review feedback
1 parent bad0172 commit b38f4bd

File tree

6 files changed

+65
-3
lines changed

6 files changed

+65
-3
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
use test_programs::p3::*;
2+
3+
struct Component;
4+
5+
export!(Component);
6+
7+
impl exports::wasi::cli::run::Guest for Component {
8+
async fn run() -> Result<(), ()> {
9+
let (mut tx, rx) = wit_stream::new();
10+
futures::join!(
11+
async {
12+
wasi::cli::stdout::write_via_stream(rx).await.unwrap();
13+
},
14+
async {
15+
tx.write_all(b"hello, world\n".to_vec()).await;
16+
wit_bindgen::spawn(async move {
17+
// Yield a few times to allow the host to accept and process
18+
// the `run` result.
19+
for _ in 0..10 {
20+
wit_bindgen::yield_async().await;
21+
}
22+
tx.write_all(b"hello again, after return\n".to_vec()).await;
23+
drop(tx);
24+
});
25+
},
26+
);
27+
Ok(())
28+
}
29+
}
30+
31+
fn main() {
32+
panic!("should call p3 entrypoint");
33+
}

crates/wasi-http/tests/all/p3/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ async fn run_cli(path: &str, server: &Server) -> anyhow::Result<()> {
144144
.await
145145
.context("failed to call `wasi:cli/run#run`")?
146146
.context("guest trapped")?
147+
.0
147148
.map_err(|()| anyhow!("`wasi:cli/run#run` failed"))
148149
}
149150

crates/wasi/src/p3/bindings.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ mod generated {
9090
"wasi:sockets/types.[method]udp-socket.connect": async | tracing | trappable,
9191
default: tracing | trappable,
9292
},
93-
exports: { default: async | store },
93+
exports: { default: async | store | task_exit },
9494
with: {
9595
"wasi:cli/terminal-input.terminal-input": crate::p3::cli::TerminalInput,
9696
"wasi:cli/terminal-output.terminal-output": crate::p3::cli::TerminalOutput,
@@ -159,7 +159,7 @@ pub use self::generated::wasi::*;
159159
/// let command = Command::instantiate_async(&mut store, &component, &linker).await?;
160160
/// let program_result = store.run_concurrent(async move |store| {
161161
/// command.wasi_cli_run().call_run(store).await
162-
/// }).await??;
162+
/// }).await??.0;
163163
/// match program_result {
164164
/// Ok(()) => Ok(()),
165165
/// Err(()) => std::process::exit(1),

crates/wasi/tests/all/p3/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async fn run_allow_blocking_current_thread(
4141
.await
4242
.context("failed to call `wasi:cli/run#run`")?
4343
.context("guest trapped")?
44+
.0
4445
.map_err(|()| anyhow!("`wasi:cli/run#run` failed"))
4546
}
4647

@@ -157,6 +158,12 @@ async fn p3_file_write_blocking() -> anyhow::Result<()> {
157158
)]
158159
fn p3_cli_hello_stdout() {}
159160

161+
#[expect(
162+
dead_code,
163+
reason = "tested in the wasi-cli crate, satisfying foreach_api! macro"
164+
)]
165+
fn p3_cli_hello_stdout_post_return() {}
166+
160167
#[expect(
161168
dead_code,
162169
reason = "tested in the wasi-cli crate, satisfying foreach_api! macro"

src/commands/run.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,11 @@ impl RunCommand {
645645
if let Ok(command) = wasmtime_wasi::p3::bindings::Command::new(&mut *store, &instance) {
646646
result = Some(
647647
store
648-
.run_concurrent(async |store| command.wasi_cli_run().call_run(store).await)
648+
.run_concurrent(async |store| {
649+
let (result, task) = command.wasi_cli_run().call_run(store).await?;
650+
task.block(store).await;
651+
Ok(result)
652+
})
649653
.await?,
650654
);
651655
}

tests/all/cli_tests.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2165,6 +2165,23 @@ start a print 1234
21652165
Ok(())
21662166
}
21672167

2168+
#[test]
2169+
fn p2_cli_p3_hello_stdout_post_return() -> Result<()> {
2170+
let output = run_wasmtime(&[
2171+
"run",
2172+
"-Wcomponent-model-async",
2173+
"-Sp3",
2174+
P3_CLI_HELLO_STDOUT_POST_RETURN_COMPONENT,
2175+
]);
2176+
if cfg!(feature = "component-model-async") {
2177+
let output = output?;
2178+
assert_eq!(output, "hello, world\nhello again, after return\n");
2179+
} else {
2180+
assert!(output.is_err());
2181+
}
2182+
Ok(())
2183+
}
2184+
21682185
mod invoke {
21692186
use super::*;
21702187

0 commit comments

Comments
 (0)