Skip to content

Commit 2ba9f11

Browse files
committed
wasi-threads: factor out process exit logic
As is being discussed [elsewhere], either calling `proc_exit` or trapping in any thread should halt execution of all threads. The Wasmtime CLI already has logic for adapting a WebAssembly error code to a code expected in each OS. This change factors out this logic to a new function, `maybe_exit_on_error`, for use within the `wasi-threads` implementation. This will work reasonably well for CLI users of Wasmtime + `wasi-threads`, but embedders will want something better in the future: when a `wasi-threads` threads fails, they may not want their application to exit. Handling this is tricky, because it will require cancelling the threads spawned by the `wasi-threads` implementation, something that is not trivial to do in Rust. With this change, we defer that work until later in order to provide a working implementation of `wasi-threads` for experimentation. [elsewhere]: WebAssembly/wasi-threads#17
1 parent b15c7fc commit 2ba9f11

File tree

7 files changed

+59
-50
lines changed

7 files changed

+59
-50
lines changed

Cargo.lock

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ wasmtime-wasi-threads = { workspace = true, optional = true }
3535
clap = { workspace = true, features = ["color", "suggestions", "derive"] }
3636
anyhow = { workspace = true }
3737
target-lexicon = { workspace = true }
38-
libc = "0.2.60"
3938
humantime = "2.0.0"
4039
once_cell = { workspace = true }
4140
listenfd = "1.0.0"

crates/wasi-threads/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ log = { workspace = true }
1717
rand = "0.8"
1818
wasi-common = { workspace = true }
1919
wasmtime = { workspace = true }
20+
wasmtime-wasi = { workspace = true }
2021

2122
[badges]
2223
maintenance = { status = "experimental" }

crates/wasi-threads/src/lib.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
use anyhow::{anyhow, Result};
66
use rand::Rng;
77
use std::thread;
8-
use wasi_common::I32Exit;
98
use wasmtime::{Caller, Linker, Module, SharedMemory, Store};
9+
use wasmtime_wasi::maybe_exit_on_error;
1010

1111
// This name is a function export designated by the wasi-threads specification:
1212
// https://github.com/WebAssembly/wasi-threads/#detailed-design-discussion
@@ -63,15 +63,11 @@ impl<T: Clone + Send + 'static> WasiThreadsCtx<T> {
6363
);
6464
match thread_entry_point.call(&mut store, (wasi_thread_id, thread_start_arg)) {
6565
Ok(_) => {}
66-
Err(trap) => {
67-
if let Some(I32Exit(0)) = trap.downcast_ref::<I32Exit>() {
68-
log::trace!(
69-
"exited thread id = {} via `wasi::proc_exit` call",
70-
wasi_thread_id
71-
)
72-
} else {
73-
panic!("{}", fail(trap.to_string()))
74-
}
66+
Err(e) => {
67+
log::trace!("exiting thread id = {} due to error", wasi_thread_id);
68+
let e = maybe_exit_on_error(e);
69+
eprintln!("Error: {:?}", e);
70+
std::process::exit(1);
7571
}
7672
}
7773
})?;

crates/wasi/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ include = ["src/**/*", "README.md", "LICENSE", "build.rs"]
1313
build = "build.rs"
1414

1515
[dependencies]
16+
libc = "0.2.60"
1617
wasi-common = { workspace = true }
1718
wasi-cap-std-sync = { workspace = true, optional = true }
1819
wasi-tokio = { workspace = true, optional = true }

crates/wasi/src/lib.rs

+42
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,45 @@ pub mod snapshots {
8484
}
8585
}
8686
}
87+
88+
/// Exit the process with a conventional OS error code as long as Wasmtime
89+
/// understands the error. If the error is not an `I32Exit` or `Trap`, return
90+
/// the error back to the caller for it to decide what to do.
91+
///
92+
/// Note: this function is designed for CLI usage of Wasmtime; embedders of
93+
/// Wasmtime may want different error handling.
94+
pub fn maybe_exit_on_error(e: anyhow::Error) -> anyhow::Error {
95+
use std::process;
96+
use wasmtime::Trap;
97+
98+
// If a specific WASI error code was requested then that's
99+
// forwarded through to the process here without printing any
100+
// extra error information.
101+
if let Some(exit) = e.downcast_ref::<I32Exit>() {
102+
// Print the error message in the usual way.
103+
// On Windows, exit status 3 indicates an abort (see below),
104+
// so return 1 indicating a non-zero status to avoid ambiguity.
105+
if cfg!(windows) && exit.0 >= 3 {
106+
process::exit(1);
107+
}
108+
process::exit(exit.0);
109+
}
110+
111+
// If the program exited because of a trap, return an error code
112+
// to the outside environment indicating a more severe problem
113+
// than a simple failure.
114+
if e.is::<Trap>() {
115+
eprintln!("Error: {:?}", e);
116+
117+
if cfg!(unix) {
118+
// On Unix, return the error code of an abort.
119+
process::exit(128 + libc::SIGABRT);
120+
} else if cfg!(windows) {
121+
// On Windows, return 3.
122+
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
123+
process::exit(3);
124+
}
125+
}
126+
127+
e
128+
}

src/commands/run.rs

+7-38
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,15 @@
33
use anyhow::{anyhow, bail, Context as _, Result};
44
use clap::Parser;
55
use once_cell::sync::Lazy;
6+
use std::ffi::OsStr;
7+
use std::path::{Component, Path, PathBuf};
68
use std::sync::Arc;
79
use std::thread;
810
use std::time::Duration;
9-
use std::{
10-
ffi::OsStr,
11-
path::{Component, Path, PathBuf},
12-
process,
13-
};
14-
use wasmtime::{Engine, Func, Linker, Module, Store, Trap, Val, ValType};
11+
use wasmtime::{Engine, Func, Linker, Module, Store, Val, ValType};
1512
use wasmtime_cli_flags::{CommonOptions, WasiModules};
13+
use wasmtime_wasi::maybe_exit_on_error;
1614
use wasmtime_wasi::sync::{ambient_authority, Dir, TcpListener, WasiCtxBuilder};
17-
use wasmtime_wasi::I32Exit;
1815

1916
#[cfg(feature = "wasi-nn")]
2017
use wasmtime_wasi_nn::WasiNnCtx;
@@ -222,38 +219,10 @@ impl RunCommand {
222219
{
223220
Ok(()) => (),
224221
Err(e) => {
225-
// If a specific WASI error code was requested then that's
226-
// forwarded through to the process here without printing any
227-
// extra error information.
228-
if let Some(exit) = e.downcast_ref::<I32Exit>() {
229-
// Print the error message in the usual way.
230-
// On Windows, exit status 3 indicates an abort (see below),
231-
// so return 1 indicating a non-zero status to avoid ambiguity.
232-
if cfg!(windows) && exit.0 >= 3 {
233-
process::exit(1);
234-
}
235-
process::exit(exit.0);
236-
}
237-
238-
// If the program exited because of a trap, return an error code
239-
// to the outside environment indicating a more severe problem
240-
// than a simple failure.
241-
if e.is::<Trap>() {
242-
eprintln!("Error: {:?}", e);
243-
244-
if cfg!(unix) {
245-
// On Unix, return the error code of an abort.
246-
process::exit(128 + libc::SIGABRT);
247-
} else if cfg!(windows) {
248-
// On Windows, return 3.
249-
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/abort?view=vs-2019
250-
process::exit(3);
251-
}
252-
}
253-
254-
// Otherwise fall back on Rust's default error printing/return
222+
// Exit the process if Wasmtime understands the error;
223+
// otherwise, fall back on Rust's default error printing/return
255224
// code.
256-
return Err(e);
225+
return Err(maybe_exit_on_error(e));
257226
}
258227
}
259228

0 commit comments

Comments
 (0)