diff --git a/lib/wasix/src/bin_factory/exec.rs b/lib/wasix/src/bin_factory/exec.rs index 2dd675796f3..1b0cd4391cf 100644 --- a/lib/wasix/src/bin_factory/exec.rs +++ b/lib/wasix/src/bin_factory/exec.rs @@ -279,6 +279,7 @@ fn call_module( if let Err(err) = call_ret { match err.downcast::() { Ok(WasiError::Exit(code)) if code.is_success() => Ok(Errno::Success), + Ok(WasiError::ThreadExit) => Ok(Errno::Success), Ok(WasiError::Exit(code)) => { runtime.on_taint(TaintReason::NonZeroExitCode(code)); Err(WasiError::Exit(code).into()) diff --git a/lib/wasix/src/lib.rs b/lib/wasix/src/lib.rs index 3f93f3c22dc..939c13c4fae 100644 --- a/lib/wasix/src/lib.rs +++ b/lib/wasix/src/lib.rs @@ -114,6 +114,8 @@ pub use crate::{ pub enum WasiError { #[error("WASI exited with code: {0}")] Exit(ExitCode), + #[error("WASI thread exited")] + ThreadExit, #[error("WASI deep sleep: {0:?}")] DeepSleep(DeepSleepWork), #[error("The WASI version could not be determined")] diff --git a/lib/wasix/src/runners/wasi.rs b/lib/wasix/src/runners/wasi.rs index e8728059bc7..b5b3c121452 100644 --- a/lib/wasix/src/runners/wasi.rs +++ b/lib/wasix/src/runners/wasi.rs @@ -404,6 +404,9 @@ impl crate::runners::Runner for WasiRunner { WasiRuntimeError::Wasi(WasiError::Exit(a)) => { WasiRuntimeError::Wasi(WasiError::Exit(*a)) } + WasiRuntimeError::Wasi(WasiError::ThreadExit) => { + WasiRuntimeError::Wasi(WasiError::ThreadExit) + } WasiRuntimeError::Wasi(WasiError::UnknownWasiVersion) => { WasiRuntimeError::Wasi(WasiError::UnknownWasiVersion) } diff --git a/lib/wasix/src/state/env.rs b/lib/wasix/src/state/env.rs index 618123fc7dc..6f60cb6735b 100644 --- a/lib/wasix/src/state/env.rs +++ b/lib/wasix/src/state/env.rs @@ -1252,32 +1252,33 @@ impl WasiEnv { /// Cleans up all the open files (if this is the main thread) #[allow(clippy::await_holding_lock)] - pub fn blocking_on_exit(&self, exit_code: Option) { - let cleanup = self.on_exit(exit_code); + pub fn blocking_on_exit(&self, process_exit_code: Option) { + let cleanup = self.on_exit(process_exit_code); InlineWaker::block_on(cleanup); } /// Cleans up all the open files (if this is the main thread) #[allow(clippy::await_holding_lock)] - pub fn on_exit(&self, exit_code: Option) -> BoxFuture<'static, ()> { + pub fn on_exit(&self, process_exit_code: Option) -> BoxFuture<'static, ()> { const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10); // If snap-shooting is enabled then we should record an event that the thread has exited. #[cfg(feature = "journal")] if self.should_journal() && self.has_active_journal() { - if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), exit_code) { + if let Err(err) = JournalEffector::save_thread_exit(self, self.tid(), process_exit_code) + { tracing::warn!("failed to save snapshot event for thread exit - {}", err); } if self.thread.is_main() { - if let Err(err) = JournalEffector::save_process_exit(self, exit_code) { + if let Err(err) = JournalEffector::save_process_exit(self, process_exit_code) { tracing::warn!("failed to save snapshot event for process exit - {}", err); } } } - // If this is the main thread then also close all the files - if self.thread.is_main() || matches!(exit_code, Some(e) if !e.is_success()) { + // If the process wants to exit, also close all files and terminate it + if let Some(process_exit_code) = process_exit_code { let process = self.process.clone(); let disable_fs_cleanup = self.disable_fs_cleanup; let pid = self.pid(); @@ -1303,8 +1304,7 @@ impl WasiEnv { } // Terminate the process - let exit_code = exit_code.unwrap_or_else(|| Errno::Canceled.into()); - process.terminate(exit_code); + process.terminate(process_exit_code); }) } else { Box::pin(async {}) diff --git a/lib/wasix/src/state/func_env.rs b/lib/wasix/src/state/func_env.rs index 5de4602234d..dfd50449723 100644 --- a/lib/wasix/src/state/func_env.rs +++ b/lib/wasix/src/state/func_env.rs @@ -284,7 +284,7 @@ impl WasiFunctionEnv { /// This function should only be called from within a syscall /// as it can potentially execute local thread variable cleanup /// code - pub fn on_exit(&self, store: &mut impl AsStoreMut, exit_code: Option) { + pub fn on_exit(&self, store: &mut impl AsStoreMut, process_exit_code: Option) { trace!( "wasi[{}:{}]::on_exit", self.data(store).pid(), @@ -292,7 +292,7 @@ impl WasiFunctionEnv { ); // Cleans up all the open files (if this is the main thread) - self.data(store).blocking_on_exit(exit_code); + self.data(store).blocking_on_exit(process_exit_code); } /// Bootstraps this main thread and context with any journals that diff --git a/lib/wasix/src/syscalls/wasix/thread_exit.rs b/lib/wasix/src/syscalls/wasix/thread_exit.rs index e6f348c7ead..c7529c99753 100644 --- a/lib/wasix/src/syscalls/wasix/thread_exit.rs +++ b/lib/wasix/src/syscalls/wasix/thread_exit.rs @@ -3,15 +3,14 @@ use crate::syscalls::*; /// ### `thread_exit()` /// Terminates the current running thread, if this is the last thread then -/// the process will also exit with the specified exit code. An exit code -/// of 0 indicates successful termination of the thread. The meanings of -/// other values is dependent on the environment. +/// the process will also exit with code 0. +/// The exit code parameter is a left over from a previous version of this +/// syscall, maintained here to keep the syscall backwards-compatible, but +/// is otherwise unused. /// -/// ## Parameters -/// -/// * `rval` - The exit code returned by the process. -#[instrument(level = "trace", skip_all, fields(%exitcode), ret)] -pub fn thread_exit(ctx: FunctionEnvMut<'_, WasiEnv>, exitcode: ExitCode) -> Result<(), WasiError> { - tracing::debug!(tid=%ctx.data().thread.id(), %exitcode); - Err(WasiError::Exit(exitcode)) +/// This syscall does not return. +#[instrument(level = "trace", skip_all, fields(%_exitcode), ret)] +pub fn thread_exit(ctx: FunctionEnvMut<'_, WasiEnv>, _exitcode: ExitCode) -> Result<(), WasiError> { + tracing::debug!(tid=%ctx.data().thread.id(), "thread exit"); + Err(WasiError::ThreadExit) } diff --git a/lib/wasix/src/syscalls/wasix/thread_spawn.rs b/lib/wasix/src/syscalls/wasix/thread_spawn.rs index 5e509923732..a6908c56bb4 100644 --- a/lib/wasix/src/syscalls/wasix/thread_spawn.rs +++ b/lib/wasix/src/syscalls/wasix/thread_spawn.rs @@ -203,11 +203,19 @@ fn call_module( .map_err(|_| Errno::Overflow) .unwrap(), ); + trace!("callback finished (ret={:?})", call_ret); + let mut ret = Errno::Success; + let mut exit_code = None; if let Err(err) = call_ret { match err.downcast::() { + Ok(WasiError::ThreadExit) => { + trace!("thread exited cleanly"); + ret = Errno::Success; + } Ok(WasiError::Exit(code)) => { - trace!(?code, "thread exited cleanly"); + trace!(exit_code = ?code, "thread requested exit"); + exit_code = Some(code); ret = if code.is_success() { Errno::Success } else { @@ -227,6 +235,7 @@ fn call_module( .runtime .on_taint(TaintReason::UnknownWasiVersion); ret = Errno::Noexec; + exit_code = Some(ExitCode::Other(128 + ret as i32)); } Err(err) => { debug!("failed with runtime error: {}", err); @@ -234,13 +243,15 @@ fn call_module( .runtime .on_taint(TaintReason::RuntimeError(err)); ret = Errno::Noexec; + exit_code = Some(ExitCode::Other(128 + ret as i32)); } } + } else { + debug!("thread exited cleanly without calling thread_exit"); } - trace!("callback finished (ret={})", ret); // Clean up the environment - env.on_exit(store, Some(ret.into())); + env.on_exit(store, exit_code); // Return the result Ok(ret as u32)