diff --git a/lib/api/src/externals/memory.rs b/lib/api/src/externals/memory.rs index cef8ef2cf9a..bc2761884de 100644 --- a/lib/api/src/externals/memory.rs +++ b/lib/api/src/externals/memory.rs @@ -74,7 +74,7 @@ impl Memory { /// Creates a view into the memory that then allows for /// read and write - pub fn view<'a>(&self, store: &'a impl AsStoreRef) -> MemoryView<'a> { + pub fn view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> { MemoryView::new(self, store) } @@ -121,13 +121,26 @@ impl Memory { self.0.grow(store, delta) } - /// Copies the memory to a new store and returns a memory reference to it + /// Attempts to duplicate this memory (if its clonable) in a new store + /// (copied memory) pub fn copy_to_store( &self, store: &impl AsStoreRef, new_store: &mut impl AsStoreMut, ) -> Result { - Ok(Self(self.0.copy_to_store(store, new_store)?)) + if !self.ty(store).shared { + // We should only be able to duplicate in a new store if the memory is shared + return Err(MemoryError::InvalidMemory { + reason: "memory is not a shared memory type".to_string(), + }); + } + self.0 + .try_clone(&store) + .and_then(|mut memory| memory.copy().ok()) + .map(|new_memory| Self::new_from_existing(new_store, new_memory.into())) + .ok_or_else(|| { + MemoryError::Generic("memory is not clonable or could not be copied".to_string()) + }) } pub(crate) fn from_vm_extern(store: &mut impl AsStoreMut, vm_extern: VMExternMemory) -> Self { @@ -145,21 +158,27 @@ impl Memory { } /// Attempts to clone this memory (if its clonable) in a new store - pub fn clone_in_store( + /// (cloned memory will be shared between those that clone it) + pub fn share_in_store( &self, store: &impl AsStoreRef, new_store: &mut impl AsStoreMut, - ) -> Option { + ) -> Result { if !self.ty(store).shared { // We should only be able to duplicate in a new store if the memory is shared - return None; + return Err(MemoryError::InvalidMemory { + reason: "memory is not a shared memory type".to_string(), + }); } self.0 .try_clone(&store) .map(|new_memory| Self::new_from_existing(new_store, new_memory)) + .ok_or_else(|| MemoryError::Generic("memory is not clonable".to_string())) } - /// Attempts to duplicate this memory (if its clonable) in a new store + /// Attempts to clone this memory (if its clonable) in a new store + /// (cloned memory will be shared between those that clone it) + #[deprecated = "use `shared_in_store` or `copy_to_store` instead"] pub fn duplicate_in_store( &self, store: &impl AsStoreRef, @@ -169,6 +188,7 @@ impl Memory { // We should only be able to duplicate in a new store if the memory is shared return None; } + #[allow(deprecated)] self.0.duplicate_in_store(store, new_store).map(Self) } diff --git a/lib/api/src/externals/memory_view.rs b/lib/api/src/externals/memory_view.rs index 519b4a60767..ebb4bfbe55b 100644 --- a/lib/api/src/externals/memory_view.rs +++ b/lib/api/src/externals/memory_view.rs @@ -21,7 +21,7 @@ use crate::sys::externals::memory_view as memory_view_impl; pub struct MemoryView<'a>(pub(crate) memory_view_impl::MemoryView<'a>); impl<'a> MemoryView<'a> { - pub(crate) fn new(memory: &Memory, store: &'a impl AsStoreRef) -> Self { + pub(crate) fn new(memory: &Memory, store: &'a (impl AsStoreRef + ?Sized)) -> Self { MemoryView(memory_view_impl::MemoryView::new(&memory.0, store)) } diff --git a/lib/api/src/js/externals/memory.rs b/lib/api/src/js/externals/memory.rs index 7d36e041a74..a33645c3c60 100644 --- a/lib/api/src/js/externals/memory.rs +++ b/lib/api/src/js/externals/memory.rs @@ -10,7 +10,7 @@ use tracing::warn; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use wasmer_types::{Pages, WASM_PAGE_SIZE}; +use wasmer_types::Pages; use super::memory_view::MemoryView; @@ -136,33 +136,6 @@ impl Memory { Ok(Pages(new_pages)) } - pub fn copy_to_store( - &self, - store: &impl AsStoreRef, - new_store: &mut impl AsStoreMut, - ) -> Result { - // Create the new memory using the parameters of the existing memory - let view = self.view(store); - let ty = self.ty(store); - let amount = view.data_size() as usize; - - let new_memory = Self::new(new_store, ty)?; - let new_view_size = new_memory.view(&new_store).data_size() as usize; - if amount > new_view_size { - let delta = amount - new_view_size; - let pages = ((delta - 1) / WASM_PAGE_SIZE) + 1; - new_memory.grow(new_store, Pages(pages as u32))?; - } - let new_view = new_memory.view(&new_store); - - // Copy the bytes - view.copy_to_memory(amount as u64, &new_view) - .map_err(|err| MemoryError::Generic(err.to_string()))?; - - // Return the new memory - Ok(new_memory) - } - pub(crate) fn from_vm_extern(_store: &mut impl AsStoreMut, internal: VMMemory) -> Self { Self { handle: internal } } @@ -171,10 +144,7 @@ impl Memory { self.handle.try_clone() } - pub fn is_from_store(&self, _store: &impl AsStoreRef) -> bool { - true - } - + #[deprecated = "use `try_clone` instead"] pub fn duplicate_in_store( &self, store: &impl AsStoreRef, @@ -185,9 +155,8 @@ impl Memory { .map(|new_memory| Self::new_from_existing(new_store, new_memory.into())) } - #[allow(unused)] - pub fn duplicate(&mut self, _store: &impl AsStoreRef) -> Result { - self.handle.duplicate() + pub fn is_from_store(&self, _store: &impl AsStoreRef) -> bool { + true } } diff --git a/lib/api/src/js/externals/memory_view.rs b/lib/api/src/js/externals/memory_view.rs index 79161575c57..fccd385ba88 100644 --- a/lib/api/src/js/externals/memory_view.rs +++ b/lib/api/src/js/externals/memory_view.rs @@ -26,7 +26,7 @@ pub struct MemoryView<'a> { } impl<'a> MemoryView<'a> { - pub(crate) fn new(memory: &Memory, _store: &'a impl AsStoreRef) -> Self { + pub(crate) fn new(memory: &Memory, _store: &'a (impl AsStoreRef + ?Sized)) -> Self { Self::new_raw(&memory.handle.memory) } diff --git a/lib/api/src/js/vm.rs b/lib/api/src/js/vm.rs index 691e4885567..31f281ab1e0 100644 --- a/lib/api/src/js/vm.rs +++ b/lib/api/src/js/vm.rs @@ -59,7 +59,13 @@ impl VMMemory { } /// Copies this memory to a new memory + #[deprecated = "use `copy` instead"] pub fn duplicate(&mut self) -> Result { + self.copy() + } + + /// Copies this memory to a new memory + pub fn copy(&mut self) -> Result { let new_memory = crate::js::externals::memory::Memory::js_memory_from_type(&self.ty)?; let src = crate::js::externals::memory_view::MemoryView::new_raw(&self.memory); diff --git a/lib/api/src/jsc/externals/memory.rs b/lib/api/src/jsc/externals/memory.rs index 443cacd64c4..f0923eb4295 100644 --- a/lib/api/src/jsc/externals/memory.rs +++ b/lib/api/src/jsc/externals/memory.rs @@ -174,16 +174,6 @@ impl Memory { self.handle.try_clone() } - pub fn duplicate_in_store( - &self, - store: &impl AsStoreRef, - new_store: &mut impl AsStoreMut, - ) -> Option { - self.try_clone(&store) - .and_then(|mut memory| memory.duplicate(&store).ok()) - .map(|new_memory| Self::new_from_existing(new_store, new_memory.into())) - } - pub fn is_from_store(&self, _store: &impl AsStoreRef) -> bool { true } diff --git a/lib/api/src/ptr.rs b/lib/api/src/ptr.rs index 206cb806315..93afcf46944 100644 --- a/lib/api/src/ptr.rs +++ b/lib/api/src/ptr.rs @@ -60,7 +60,7 @@ pub type WasmPtr64 = WasmPtr; #[repr(transparent)] pub struct WasmPtr { offset: M::Offset, - _phantom: PhantomData<*mut T>, + _phantom: PhantomData, } impl WasmPtr { diff --git a/lib/api/src/store.rs b/lib/api/src/store.rs index e2d2c7ae3b2..4ebc1ee2ef5 100644 --- a/lib/api/src/store.rs +++ b/lib/api/src/store.rs @@ -191,6 +191,7 @@ impl fmt::Debug for Store { } /// A temporary handle to a [`Store`]. +#[derive(Debug)] pub struct StoreRef<'a> { pub(crate) inner: &'a StoreInner, } diff --git a/lib/api/src/sys/externals/memory.rs b/lib/api/src/sys/externals/memory.rs index ef73e31eeaf..f09a8453e85 100644 --- a/lib/api/src/sys/externals/memory.rs +++ b/lib/api/src/sys/externals/memory.rs @@ -39,12 +39,6 @@ impl Memory { self.handle.get(store.as_store_ref().objects()).ty() } - /// Creates a view into the memory that then allows for - /// read and write - pub fn view<'a>(&self, store: &'a impl AsStoreRef) -> MemoryView<'a> { - MemoryView::new(self, store) - } - pub fn grow( &self, store: &mut impl AsStoreMut, @@ -56,34 +50,6 @@ impl Memory { self.handle.get_mut(store.objects_mut()).grow(delta.into()) } - pub fn copy_to_store( - &self, - store: &impl AsStoreRef, - new_store: &mut impl AsStoreMut, - ) -> Result { - // Create the new memory using the parameters of the existing memory - let view = self.view(store); - let ty = self.ty(store); - let amount = view.data_size() as usize; - - let new_memory = Self::new(new_store, ty)?; - let mut new_view = new_memory.view(&new_store); - let new_view_size = new_view.data_size() as usize; - if amount > new_view_size { - let delta = amount - new_view_size; - let pages = ((delta - 1) / wasmer_types::WASM_PAGE_SIZE) + 1; - new_memory.grow(new_store, Pages(pages as u32))?; - new_view = new_memory.view(&new_store); - } - - // Copy the bytes - view.copy_to_memory(amount as u64, &new_view) - .map_err(|err| MemoryError::Generic(err.to_string()))?; - - // Return the new memory - Ok(new_memory) - } - pub(crate) fn from_vm_extern(store: &impl AsStoreRef, vm_extern: VMExternMemory) -> Self { Self { handle: unsafe { @@ -102,13 +68,14 @@ impl Memory { mem.try_clone().map(|mem| mem.into()) } + #[deprecated = "use `try_clone` instead"] pub fn duplicate_in_store( &self, store: &impl AsStoreRef, new_store: &mut impl AsStoreMut, ) -> Option { self.try_clone(&store) - .and_then(|mut memory| memory.duplicate().ok()) + .and_then(|mut memory| memory.copy().ok()) .map(|new_memory| Self::new_from_existing(new_store, new_memory.into())) } diff --git a/lib/api/src/sys/externals/memory_view.rs b/lib/api/src/sys/externals/memory_view.rs index 41ea4e70674..a506abd9dc2 100644 --- a/lib/api/src/sys/externals/memory_view.rs +++ b/lib/api/src/sys/externals/memory_view.rs @@ -22,7 +22,7 @@ pub struct MemoryView<'a> { } impl<'a> MemoryView<'a> { - pub(crate) fn new(memory: &Memory, store: &'a impl AsStoreRef) -> Self { + pub(crate) fn new(memory: &Memory, store: &'a (impl AsStoreRef + ?Sized)) -> Self { let size = memory.handle.get(store.as_store_ref().objects()).size(); let definition = memory.handle.get(store.as_store_ref().objects()).vmmemory(); diff --git a/lib/api/src/sys/tunables.rs b/lib/api/src/sys/tunables.rs index 80111464ff8..708d320c879 100644 --- a/lib/api/src/sys/tunables.rs +++ b/lib/api/src/sys/tunables.rs @@ -6,6 +6,7 @@ pub use wasmer_compiler::BaseTunables; #[cfg(test)] mod tests { use super::*; + #[allow(unused)] use crate::sys::NativeEngineExt; use crate::TableType; use std::cell::UnsafeCell; @@ -123,7 +124,7 @@ mod tests { None } - fn duplicate(&mut self) -> Result, MemoryError> { + fn copy(&mut self) -> Result, MemoryError> { let mem = self.mem.clone(); Ok(Box::new(Self { memory_definition: Some(UnsafeCell::new(VMMemoryDefinition { @@ -243,6 +244,7 @@ mod tests { let compiler = Cranelift::default(); let tunables = TinyTunables {}; + #[allow(deprecated)] let mut engine = Engine::new(compiler.into(), Default::default(), Default::default()); engine.set_tunables(tunables); let mut store = Store::new(engine); diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index d4903c7ac8a..1ca9cd55d92 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -1,3 +1,4 @@ +use crate::commands::run::wasi::RunProperties; use crate::common::get_cache_dir; use crate::logging; use crate::package_source::PackageSource; @@ -11,7 +12,7 @@ use std::fs::File; use std::io::Write; use std::net::SocketAddr; use std::ops::Deref; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::str::FromStr; use wasmer::FunctionEnv; use wasmer::*; @@ -191,36 +192,54 @@ impl RunWithPathBuf { } } - fn inner_module_run(&self, store: &mut Store, instance: Instance) -> Result { + fn inner_module_init(&self, store: &mut Store, instance: &Instance) -> Result<()> { #[cfg(feature = "sys")] if self.stack_size.is_some() { wasmer_vm::set_stack_size(self.stack_size.unwrap()); } + // If this module exports an _initialize function, run that first. if let Ok(initialize) = instance.exports.get_function("_initialize") { initialize .call(store, &[]) .with_context(|| "failed to run _initialize function")?; } + Ok(()) + } + + fn inner_module_invoke_function( + store: &mut Store, + instance: &Instance, + path: &Path, + invoke: &str, + args: &[String], + ) -> Result<()> { + let result = Self::invoke_function(store, instance, path, invoke, args)?; + println!( + "{}", + result + .iter() + .map(|val| val.to_string()) + .collect::>() + .join(" ") + ); + Ok(()) + } + fn inner_module_run(&self, store: &mut Store, instance: &Instance) -> Result { // Do we want to invoke a function? if let Some(ref invoke) = self.invoke { - let result = self.invoke_function(store, &instance, invoke, &self.args)?; - println!( - "{}", - result - .iter() - .map(|val| val.to_string()) - .collect::>() - .join(" ") - ); + Self::inner_module_invoke_function( + store, + instance, + self.path.as_path(), + invoke, + &self.args, + )?; } else { - let start: Function = self.try_find_function(&instance, "_start", &[])?; - let result = start.call(store, &[]); - #[cfg(feature = "wasi")] - return self.wasi.handle_result(result); - #[cfg(not(feature = "wasi"))] - return Ok(result?); + let start: Function = + Self::try_find_function(instance, self.path.as_path(), "_start", &[])?; + start.call(store, &[])?; } Ok(0) @@ -330,16 +349,24 @@ impl RunWithPathBuf { .wasi .instantiate(&mut store, &module, program_name, self.args.clone()) .with_context(|| "failed to instantiate WASI module")?; - let res = self.inner_module_run(&mut store, instance); - ctx.cleanup(&mut store, None); - - res + let capable_of_deep_sleep = ctx.data(&store).capable_of_deep_sleep(); + ctx.data_mut(&mut store) + .enable_deep_sleep = capable_of_deep_sleep; + + self.inner_module_init(&mut store, &instance)?; + Wasi::run( + RunProperties { + ctx, path: self.path.clone(), invoke: self.invoke.clone(), args: self.args.clone() + }, + store + ) } // not WASI _ => { let instance = Instance::new(&mut store, &module, &imports! {})?; - self.inner_module_run(&mut store, instance) + self.inner_module_init(&mut store, &instance)?; + self.inner_module_run(&mut store, &instance) } } }.map(|exit_code| { @@ -361,7 +388,8 @@ impl RunWithPathBuf { // Do we want to invoke a function? if let Some(ref invoke) = self.invoke { - let result = self.invoke_function(&instance, invoke, &self.args)?; + let result = + Self::invoke_function(&instance, self.path.as_path(), invoke, &self.args)?; println!( "{}", result @@ -371,7 +399,8 @@ impl RunWithPathBuf { .join(" ") ); } else { - let start: Function = self.try_find_function(&instance, "_start", &[])?; + let start: Function = + Self.try_find_function(&instance, self.path.as_path(), "_start", &[])?; let result = start.call(&[]); #[cfg(feature = "wasi")] self.wasi.handle_result(result)?; @@ -519,8 +548,8 @@ impl RunWithPathBuf { } fn try_find_function( - &self, instance: &Instance, + path: &Path, name: &str, args: &[String], ) -> Result { @@ -540,7 +569,7 @@ impl RunWithPathBuf { .join(", "); let suggested_command = format!( "wasmer {} -i {} {}", - self.path.display(), + path.display(), suggested_functions.get(0).unwrap_or(&String::new()), args.join(" ") ); @@ -568,13 +597,13 @@ impl RunWithPathBuf { } fn invoke_function( - &self, ctx: &mut impl AsStoreMut, instance: &Instance, + path: &Path, invoke: &str, args: &[String], ) -> Result> { - let func: Function = self.try_find_function(instance, invoke, args)?; + let func: Function = Self::try_find_function(instance, path, invoke, args)?; let func_ty = func.ty(ctx); let required_arguments = func_ty.params().len(); let provided_arguments = args.len(); @@ -583,7 +612,7 @@ impl RunWithPathBuf { "Function expected {} arguments, but received {}: \"{}\"", required_arguments, provided_arguments, - self.args.join(" ") + args.join(" ") ); } let invoke_args = args diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index 8de9efe3f2d..893c0a613ef 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -3,15 +3,18 @@ use anyhow::{Context, Result}; use std::{ collections::{BTreeSet, HashMap}, path::{Path, PathBuf}, - sync::Arc, + sync::{mpsc::Sender, Arc}, }; use virtual_fs::{DeviceFile, FileSystem, PassthruFileSystem, RootFileSystemBuilder}; -use wasmer::{AsStoreMut, Engine, Instance, Module, RuntimeError, Value}; +use wasmer::{ + AsStoreMut, Engine, Function, Instance, Memory32, Memory64, Module, RuntimeError, Store, Value, +}; use wasmer_registry::WasmerConfig; use wasmer_wasix::{ bin_factory::BinaryPackage, default_fs_backing, get_wasi_versions, os::{tty_sys::SysTty, TtyBridge}, + rewind, runners::MappedDirectory, runtime::{ module_cache::{FileSystemCache, ModuleCache}, @@ -19,11 +22,15 @@ use wasmer_wasix::{ task_manager::tokio::TokioTaskManager, }, types::__WASI_STDIN_FILENO, - PluggableRuntime, WasiEnv, WasiEnvBuilder, WasiError, WasiFunctionEnv, WasiVersion, + wasmer_wasix_types::wasi::Errno, + PluggableRuntime, RewindState, WasiEnv, WasiEnvBuilder, WasiError, WasiFunctionEnv, + WasiVersion, }; use clap::Parser; +use super::RunWithPathBuf; + #[derive(Debug, Parser, Clone, Default)] /// WASI Options pub struct Wasi { @@ -82,6 +89,10 @@ pub struct Wasi { #[clap(long = "no-tty")] pub no_tty: bool, + /// Enables asynchronous threading + #[clap(long = "enable-async-threads")] + pub enable_async_threads: bool, + /// Allow instances to send http requests. /// /// Access to domains is granted by default. @@ -97,6 +108,13 @@ pub struct Wasi { pub deny_multiple_wasi_versions: bool, } +pub struct RunProperties { + pub ctx: WasiFunctionEnv, + pub path: PathBuf, + pub invoke: Option, + pub args: Vec, +} + #[allow(dead_code)] impl Wasi { pub fn map_dir(&mut self, alias: &str, target_on_disk: PathBuf) { @@ -195,6 +213,11 @@ impl Wasi { builder.capabilities_mut().http_client = caps; } + builder + .capabilities_mut() + .threading + .enable_asynchronous_threading = self.enable_async_threads; + #[cfg(feature = "experimental-io-devices")] { if self.enable_experimental_io_devices { @@ -249,21 +272,164 @@ impl Wasi { Ok((wasi_env, instance)) } + // Runs the Wasi process + pub fn run(run: RunProperties, store: Store) -> Result { + let tasks = run.ctx.data(&store).tasks().clone(); + + // The return value is passed synchronously and will block until the result is returned + // this is because the main thread can go into a deep sleep and exit the dedicated thread + let (tx, rx) = std::sync::mpsc::channel(); + + // We run it in a blocking thread as the WASM function may otherwise hold + // up the IO operations + tasks.task_dedicated(Box::new(move || { + Self::run_with_deep_sleep(run, store, tx, None); + }))?; + rx.recv() + .expect("main thread terminated without a result, this normally means a panic occurred within the main thread") + } + + // Runs the Wasi process (asynchronously) + pub fn run_with_deep_sleep( + run: RunProperties, + mut store: Store, + tx: Sender>, + rewind_state: Option<(RewindState, Result<(), Errno>)>, + ) { + // If we need to rewind then do so + let ctx = run.ctx; + if let Some((mut rewind_state, trigger_res)) = rewind_state { + if rewind_state.is_64bit { + if let Err(exit_code) = + rewind_state.rewinding_finish::(&ctx, &mut store, trigger_res) + { + tx.send(Ok(exit_code.raw())).ok(); + return; + } + let res = rewind::( + ctx.env.clone().into_mut(&mut store), + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + ); + if res != Errno::Success { + tx.send(Ok(res as i32)).ok(); + return; + } + } else { + if let Err(exit_code) = + rewind_state.rewinding_finish::(&ctx, &mut store, trigger_res) + { + tx.send(Ok(exit_code.raw())).ok(); + return; + } + let res = rewind::( + ctx.env.clone().into_mut(&mut store), + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + ); + if res != Errno::Success { + tx.send(Ok(res as i32)).ok(); + return; + } + } + } + + // Get the instance from the environment + let instance = match ctx.data(&store).try_clone_instance() { + Some(inst) => inst, + None => { + tx.send(Ok(Errno::Noexec as i32)).ok(); + return; + } + }; + + // Do we want to invoke a function? + if let Some(ref invoke) = run.invoke { + let res = RunWithPathBuf::inner_module_invoke_function( + &mut store, + &instance, + run.path.as_path(), + invoke, + &run.args, + ) + .map(|()| 0); + + ctx.cleanup(&mut store, None); + + tx.send(res).unwrap(); + } else { + let start: Function = + RunWithPathBuf::try_find_function(&instance, run.path.as_path(), "_start", &[]) + .unwrap(); + + let result = start.call(&mut store, &[]); + Self::handle_result( + RunProperties { + ctx, + path: run.path, + invoke: run.invoke, + args: run.args, + }, + store, + result, + tx, + ) + } + } + /// Helper function for handling the result of a Wasi _start function. - pub fn handle_result(&self, result: Result, RuntimeError>) -> Result { - match result { + pub fn handle_result( + run: RunProperties, + mut store: Store, + result: Result, RuntimeError>, + tx: Sender>, + ) { + let ctx = run.ctx; + let ret: Result = match result { Ok(_) => Ok(0), Err(err) => { - let err: anyhow::Error = match err.downcast::() { - Ok(WasiError::Exit(exit_code)) => { - return Ok(exit_code.raw()); + match err.downcast::() { + Ok(WasiError::Exit(exit_code)) => Ok(exit_code.raw()), + Ok(WasiError::DeepSleep(deep)) => { + let pid = ctx.data(&store).pid(); + let tid = ctx.data(&store).tid(); + tracing::trace!(%pid, %tid, "entered a deep sleep"); + + // Create the respawn function + let tasks = ctx.data(&store).tasks().clone(); + let rewind = deep.rewind; + let respawn = { + let path = run.path; + let invoke = run.invoke; + let args = run.args; + move |ctx, store, res| { + let run = RunProperties { + ctx, + path, + invoke, + args, + }; + Self::run_with_deep_sleep(run, store, tx, Some((rewind, res))); + } + }; + + // Spawns the WASM process after a trigger + tasks + .resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger) + .unwrap(); + return; } - Ok(err) => err.into(), - Err(err) => err.into(), - }; - Err(err) + Ok(err) => Err(err.into()), + Err(err) => Err(err.into()), + } } - } + }; + + ctx.cleanup(&mut store, None); + + tx.send(ret).unwrap(); } pub fn for_binfmt_interpreter() -> Result { diff --git a/lib/sys-utils/src/memory/fd_memory/memories.rs b/lib/sys-utils/src/memory/fd_memory/memories.rs index 1699e1bea1c..25296d963c2 100644 --- a/lib/sys-utils/src/memory/fd_memory/memories.rs +++ b/lib/sys-utils/src/memory/fd_memory/memories.rs @@ -133,7 +133,7 @@ impl WasmMmap { /// Copies the memory /// (in this case it performs a copy-on-write to save memory) - pub fn duplicate(&mut self) -> Result { + pub fn copy(&mut self) -> Result { let mem_length = self.size.bytes().0; let mut alloc = self .alloc @@ -304,9 +304,9 @@ impl VMOwnedMemory { } /// Copies this memory to a new memory - pub fn duplicate(&mut self) -> Result { + pub fn copy(&mut self) -> Result { Ok(Self { - mmap: self.mmap.duplicate()?, + mmap: self.mmap.copy()?, config: self.config.clone(), }) } @@ -349,8 +349,8 @@ impl LinearMemory for VMOwnedMemory { } /// Copies this memory to a new memory - fn duplicate(&mut self) -> Result, MemoryError> { - let forked = Self::duplicate(self)?; + fn copy(&mut self) -> Result, MemoryError> { + let forked = Self::copy(self)?; Ok(Box::new(forked)) } } @@ -393,10 +393,10 @@ impl VMSharedMemory { } /// Copies this memory to a new memory - pub fn duplicate(&mut self) -> Result { + pub fn copy(&mut self) -> Result { let mut guard = self.mmap.write().unwrap(); Ok(Self { - mmap: Arc::new(RwLock::new(guard.duplicate()?)), + mmap: Arc::new(RwLock::new(guard.copy()?)), config: self.config.clone(), conditions: ThreadConditions::new(), }) @@ -445,8 +445,8 @@ impl LinearMemory for VMSharedMemory { } /// Copies this memory to a new memory - fn duplicate(&mut self) -> Result, MemoryError> { - let forked = Self::duplicate(self)?; + fn copy(&mut self) -> Result, MemoryError> { + let forked = Self::copy(self)?; Ok(Box::new(forked)) } @@ -525,8 +525,8 @@ impl LinearMemory for VMMemory { } /// Copies this memory to a new memory - fn duplicate(&mut self) -> Result, MemoryError> { - self.0.duplicate() + fn copy(&mut self) -> Result, MemoryError> { + self.0.copy() } } @@ -588,8 +588,8 @@ impl VMMemory { } /// Copies this memory to a new memory - pub fn duplicate(&mut self) -> Result, MemoryError> { - LinearMemory::duplicate(self) + pub fn copy(&mut self) -> Result, MemoryError> { + LinearMemory::copy(self) } } diff --git a/lib/types/src/memory.rs b/lib/types/src/memory.rs index 3f2cab59cc2..66c09654317 100644 --- a/lib/types/src/memory.rs +++ b/lib/types/src/memory.rs @@ -109,6 +109,9 @@ pub unsafe trait MemorySize: Copy { /// Convert a `Native` to an `Offset`. fn native_to_offset(native: Self::Native) -> Self::Offset; + + /// True if the memory is 64-bit + fn is_64bit() -> bool; } /// Marker trait for 32-bit memories. @@ -125,6 +128,9 @@ unsafe impl MemorySize for Memory32 { fn native_to_offset(native: Self::Native) -> Self::Offset { native as Self::Offset } + fn is_64bit() -> bool { + false + } } /// Marker trait for 64-bit memories. @@ -141,4 +147,7 @@ unsafe impl MemorySize for Memory64 { fn native_to_offset(native: Self::Native) -> Self::Offset { native as Self::Offset } + fn is_64bit() -> bool { + true + } } diff --git a/lib/types/src/store_id.rs b/lib/types/src/store_id.rs index cd25a8ef3b0..414a4af1d0f 100644 --- a/lib/types/src/store_id.rs +++ b/lib/types/src/store_id.rs @@ -1,3 +1,4 @@ +use core::fmt::Display; use std::{ num::NonZeroUsize, sync::atomic::{AtomicUsize, Ordering}, @@ -20,3 +21,14 @@ impl Default for StoreId { Self(NonZeroUsize::new(NEXT_ID.fetch_add(1, Ordering::Relaxed)).unwrap()) } } + +impl Display for StoreId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let val: usize = self.0.into(); + if val == usize::MAX { + write!(f, "unknown") + } else { + write!(f, "{}", self.0) + } + } +} diff --git a/lib/vm/src/instance/mod.rs b/lib/vm/src/instance/mod.rs index 52b0a9e0f00..fe327c3fecc 100644 --- a/lib/vm/src/instance/mod.rs +++ b/lib/vm/src/instance/mod.rs @@ -837,7 +837,7 @@ impl Instance { if let Ok(mut ret) = ret { if ret == 0 { let memory = self.get_local_vmmemory_mut(memory_index); - ret = Instance::memory_wait(memory, dst, timeout)?; + ret = Self::memory_wait(memory, dst, timeout)?; } Ok(ret) } else { @@ -863,7 +863,7 @@ impl Instance { if let Ok(mut ret) = ret { if ret == 0 { let memory = self.get_vmmemory_mut(memory_index); - ret = Instance::memory_wait(memory, dst, timeout)?; + ret = Self::memory_wait(memory, dst, timeout)?; } Ok(ret) } else { @@ -889,7 +889,7 @@ impl Instance { if let Ok(mut ret) = ret { if ret == 0 { let memory = self.get_local_vmmemory_mut(memory_index); - ret = Instance::memory_wait(memory, dst, timeout)?; + ret = Self::memory_wait(memory, dst, timeout)?; } Ok(ret) } else { @@ -916,7 +916,7 @@ impl Instance { if let Ok(mut ret) = ret { if ret == 0 { let memory = self.get_vmmemory_mut(memory_index); - ret = Instance::memory_wait(memory, dst, timeout)?; + ret = Self::memory_wait(memory, dst, timeout)?; } Ok(ret) } else { diff --git a/lib/vm/src/memory.rs b/lib/vm/src/memory.rs index a55b242f809..e31b594c0c9 100644 --- a/lib/vm/src/memory.rs +++ b/lib/vm/src/memory.rs @@ -122,11 +122,11 @@ impl WasmMmap { /// Copies the memory /// (in this case it performs a copy-on-write to save memory) - pub fn duplicate(&mut self) -> Result { + pub fn copy(&mut self) -> Result { let mem_length = self.size.bytes().0; let mut alloc = self .alloc - .duplicate(Some(mem_length)) + .copy(Some(mem_length)) .map_err(MemoryError::Generic)?; let base_ptr = alloc.as_mut_ptr(); Ok(Self { @@ -293,9 +293,9 @@ impl VMOwnedMemory { } /// Copies this memory to a new memory - pub fn duplicate(&mut self) -> Result { + pub fn copy(&mut self) -> Result { Ok(Self { - mmap: self.mmap.duplicate()?, + mmap: self.mmap.copy()?, config: self.config.clone(), }) } @@ -337,8 +337,8 @@ impl LinearMemory for VMOwnedMemory { } /// Copies this memory to a new memory - fn duplicate(&mut self) -> Result, MemoryError> { - let forked = Self::duplicate(self)?; + fn copy(&mut self) -> Result, MemoryError> { + let forked = Self::copy(self)?; Ok(Box::new(forked)) } } @@ -382,10 +382,10 @@ impl VMSharedMemory { } /// Copies this memory to a new memory - pub fn duplicate(&mut self) -> Result { + pub fn copy(&mut self) -> Result { let mut guard = self.mmap.write().unwrap(); Ok(Self { - mmap: Arc::new(RwLock::new(guard.duplicate()?)), + mmap: Arc::new(RwLock::new(guard.copy()?)), config: self.config.clone(), conditions: ThreadConditions::new(), }) @@ -434,8 +434,8 @@ impl LinearMemory for VMSharedMemory { } /// Copies this memory to a new memory - fn duplicate(&mut self) -> Result, MemoryError> { - let forked = Self::duplicate(self)?; + fn copy(&mut self) -> Result, MemoryError> { + let forked = Self::copy(self)?; Ok(Box::new(forked)) } @@ -516,8 +516,8 @@ impl LinearMemory for VMMemory { } /// Copies this memory to a new memory - fn duplicate(&mut self) -> Result, MemoryError> { - self.0.duplicate() + fn copy(&mut self) -> Result, MemoryError> { + self.0.copy() } // Add current thread to waiter list @@ -593,8 +593,8 @@ impl VMMemory { } /// Copies this memory to a new memory - pub fn duplicate(&mut self) -> Result, MemoryError> { - LinearMemory::duplicate(self) + pub fn copy(&mut self) -> Result, MemoryError> { + LinearMemory::copy(self) } } @@ -650,7 +650,7 @@ where } /// Copies this memory to a new memory - fn duplicate(&mut self) -> Result, MemoryError>; + fn copy(&mut self) -> Result, MemoryError>; /// Add current thread to the waiter hash, and wait until notified or timout. /// Return 0 if the waiter has been notified, 2 if the timeout occured, or None if en error happened diff --git a/lib/vm/src/mmap.rs b/lib/vm/src/mmap.rs index abeb21a666b..fb3be54d509 100644 --- a/lib/vm/src/mmap.rs +++ b/lib/vm/src/mmap.rs @@ -286,7 +286,13 @@ impl Mmap { } /// Duplicate in a new memory mapping. + #[deprecated = "use `copy` instead"] pub fn duplicate(&mut self, size_hint: Option) -> Result { + self.copy(size_hint) + } + + /// Duplicate in a new memory mapping. + pub fn copy(&mut self, size_hint: Option) -> Result { // NOTE: accessible_size != used size as the value is not // automatically updated when the pre-provisioned space is used let mut copy_size = self.accessible_size; diff --git a/lib/wasi-types/src/wasi/wasix_manual.rs b/lib/wasi-types/src/wasi/wasix_manual.rs index bd96ac690a4..c33465221eb 100644 --- a/lib/wasi-types/src/wasi/wasix_manual.rs +++ b/lib/wasi-types/src/wasi/wasix_manual.rs @@ -179,7 +179,7 @@ unsafe impl ValueType for StackSnapshot { #[repr(C)] #[derive(Clone, Copy)] pub union JoinStatusUnion { - pub nothing: u8, + pub nothing_errno: Errno, pub exit_normal: Errno, pub exit_signal: ErrnoSignal, pub stopped: Signal, @@ -196,7 +196,7 @@ impl core::fmt::Debug for JoinStatus { let mut f = binding.field("tag", &self.tag); f = unsafe { match self.tag { - JoinStatusType::Nothing => f.field("pid", &self.u.nothing), + JoinStatusType::Nothing => f.field("nothing_errno", &self.u.nothing_errno), JoinStatusType::ExitNormal => f.field("exit_normal", &self.u.exit_normal), JoinStatusType::ExitSignal => f.field("exit_signal", &self.u.exit_signal), JoinStatusType::Stopped => f.field("stopped", &self.u.stopped), @@ -214,7 +214,7 @@ unsafe impl ValueType for JoinStatus { #[repr(C)] #[derive(Copy, Clone)] pub struct ThreadStart { - pub stack_start: M::Offset, + pub stack_upper: M::Offset, pub tls_base: M::Offset, pub start_funct: M::Offset, pub start_args: M::Offset, @@ -225,7 +225,7 @@ pub struct ThreadStart { impl core::fmt::Debug for ThreadStart { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("ThreadStart") - .field("stack_start", &self.stack_start) + .field("stack_upper", &self.stack_upper) .field("tls-base", &self.tls_base) .field("start-funct", &self.start_funct) .field("start-args", &self.start_args) diff --git a/lib/wasi-web/.gitignore b/lib/wasi-web/.gitignore index 87edd248acb..bac5fc96dcd 100644 --- a/lib/wasi-web/.gitignore +++ b/lib/wasi-web/.gitignore @@ -2,6 +2,7 @@ node_modules dist .parcel-cache .cache +.vscode target pkg wapm/*.wasm diff --git a/lib/wasi-web/Cargo.lock b/lib/wasi-web/Cargo.lock index 83dfcce4310..f86357bc294 100644 --- a/lib/wasi-web/Cargo.lock +++ b/lib/wasi-web/Cargo.lock @@ -75,7 +75,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -337,7 +337,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -354,7 +354,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -587,7 +587,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1376,9 +1376,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] @@ -1406,20 +1406,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -1602,9 +1602,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -1662,7 +1662,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -1749,7 +1749,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.15", ] [[package]] @@ -2453,9 +2453,9 @@ dependencies = [ [[package]] name = "wast" -version = "55.0.0" +version = "56.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4984d3e1406571f4930ba5cf79bd70f75f41d0e87e17506e0bd19b0e5d085f05" +checksum = "6b54185c051d7bbe23757d50fe575880a2426a2f06d2e9f6a10fd9a4a42920c0" dependencies = [ "leb128", "memchr", @@ -2465,9 +2465,9 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.61" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af2b53f4da14db05d32e70e9c617abdf6620c575bd5dd972b7400037b4df2091" +checksum = "56681922808216ab86d96bb750f70d500b5a7800e41564290fd46bb773581299" dependencies = [ "wast", ] diff --git a/lib/wasi-web/Cargo.toml b/lib/wasi-web/Cargo.toml index f438eb3b330..15304e6d17d 100644 --- a/lib/wasi-web/Cargo.toml +++ b/lib/wasi-web/Cargo.toml @@ -18,8 +18,8 @@ wasm-bindgen = { version = "0.2", features = [ "serde-serialize" ] } wasm-bindgen-futures = "0.4" console_error_panic_hook = "^0.1" js-sys = "0.3" -tracing = { version = "^0.1", features = [ "log", "release_max_level_info" ] } -#tracing = { version = "^0.1", features = [ "log" ] } +#tracing = { version = "^0.1", features = [ "log", "release_max_level_info" ] } +tracing = { version = "^0.1", features = [ "log" ] } tracing-futures = { version = "^0.2" } tracing-subscriber = { version = "^0.2" } tracing-wasm = { version = "^0.2" } diff --git a/lib/wasi-web/package-lock.json b/lib/wasi-web/package-lock.json index 2707e9d5144..e544be653ba 100644 --- a/lib/wasi-web/package-lock.json +++ b/lib/wasi-web/package-lock.json @@ -28,12 +28,12 @@ } }, "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { @@ -41,9 +41,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "dev": true, "dependencies": { "@babel/highlight": "^7.18.6" @@ -53,30 +53,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", - "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.0", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.0", + "@babel/parser": "^7.21.4", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -113,12 +113,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.21.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", - "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", "dev": true, "dependencies": { - "@babel/types": "^7.21.0", + "@babel/types": "^7.21.4", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -127,20 +127,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", @@ -167,13 +153,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -195,9 +181,9 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", - "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz", + "integrity": "sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", @@ -217,9 +203,9 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz", - "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz", + "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", @@ -317,12 +303,12 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.21.4" }, "engines": { "node": ">=6.9.0" @@ -510,9 +496,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -866,12 +852,12 @@ } }, "node_modules/@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz", + "integrity": "sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -908,12 +894,12 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -1126,9 +1112,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -1380,9 +1366,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2" @@ -1567,31 +1553,31 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.4.tgz", + "integrity": "sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", + "@babel/compat-data": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", + "@babel/helper-validator-option": "^7.21.0", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", "@babel/plugin-proposal-dynamic-import": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -1608,40 +1594,40 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.20.7", + "@babel/plugin-transform-async-to-generator": "^7.20.7", "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.20.7", + "@babel/plugin-transform-destructuring": "^7.21.3", "@babel/plugin-transform-dotall-regex": "^7.18.6", "@babel/plugin-transform-duplicate-keys": "^7.18.9", "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-for-of": "^7.21.0", "@babel/plugin-transform-function-name": "^7.18.9", "@babel/plugin-transform-literals": "^7.18.9", "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.2", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", "@babel/plugin-transform-new-target": "^7.18.6", "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-parameters": "^7.21.3", "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.20.5", "@babel/plugin-transform-reserved-words": "^7.18.6", "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-spread": "^7.20.7", "@babel/plugin-transform-sticky-regex": "^7.18.6", "@babel/plugin-transform-template-literals": "^7.18.9", "@babel/plugin-transform-typeof-symbol": "^7.18.9", "@babel/plugin-transform-unicode-escapes": "^7.18.10", "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", + "@babel/types": "^7.21.4", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -1713,19 +1699,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.1", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.2", - "@babel/types": "^7.21.2", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1734,9 +1720,9 @@ } }, "node_modules/@babel/types": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.19.4", @@ -1763,13 +1749,14 @@ "dev": true }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" }, "engines": { "node": ">=6.0.0" @@ -1794,45 +1781,37 @@ } }, "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" } }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" } }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", @@ -1994,9 +1973,9 @@ } }, "node_modules/@types/eslint": { - "version": "8.21.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", - "integrity": "sha512-rc9K8ZpVjNcLs8Fp0dkozd5Pt2Apk1glO4Vgz8ix1u6yFByxfqo5Yavpy65o+93TAe24jr7v+eSBtFLvOQtCRQ==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", "dev": true, "dependencies": { "@types/estree": "*", @@ -2014,9 +1993,9 @@ } }, "node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", "dev": true }, "node_modules/@types/express": { @@ -2070,9 +2049,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.15.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz", - "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==", + "version": "18.15.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", + "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", "dev": true }, "node_modules/@types/q": { @@ -2710,6 +2689,19 @@ "node": ">=0.10.0" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", @@ -3147,9 +3139,9 @@ } }, "node_modules/bonjour-service": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz", - "integrity": "sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", "dev": true, "dependencies": { "array-flatten": "^2.1.2", @@ -3278,9 +3270,9 @@ } }, "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -3470,9 +3462,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001464", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001464.tgz", - "integrity": "sha512-oww27MtUmusatpRpCGSOneQk2/l5czXANDSFvsc7VuOQ86s3ANhZetpwXNf1zY/zdfP63Xvjz325DAdAoES13g==", + "version": "1.0.30001478", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz", + "integrity": "sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw==", "dev": true, "funding": [ { @@ -3482,6 +3474,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -4100,9 +4096,9 @@ "hasInstallScript": true }, "node_modules/core-js-compat": { - "version": "3.29.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.0.tgz", - "integrity": "sha512-ScMn3uZNAFhK2DGoEfErguoiAHhV2Ju+oJo/jK08p7B3f3UhocUrCCkTvnZaiS+edl5nlIoiBXKcwMc6elv4KQ==", + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.1.tgz", + "integrity": "sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw==", "dev": true, "dependencies": { "browserslist": "^4.21.5" @@ -4846,9 +4842,9 @@ "dev": true }, "node_modules/dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.5.0.tgz", + "integrity": "sha512-USawdAUzRkV6xrqTjiAEp6M9YagZEzWcSUaZTcIFAiyQWW1SoI6KyId8y2+/71wbgHKQAKd+iupLv4YvEwYWvA==", "dev": true, "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" @@ -4995,9 +4991,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.327", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.327.tgz", - "integrity": "sha512-DIk2H4g/3ZhjgiABJjVdQvUdMlSABOsjeCm6gmUzIdKxAuFrGiJ8QXMm3i09grZdDBMC/d8MELMrdwYRC0+YHg==", + "version": "1.4.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.363.tgz", + "integrity": "sha512-ReX5qgmSU7ybhzMuMdlJAdYnRhT90UB3k9M05O5nF5WH3wR5wgdJjXw0uDeFyKNhmglmQiOxkAbzrP0hMKM59g==", "dev": true }, "node_modules/elliptic": { @@ -5074,18 +5070,18 @@ } }, "node_modules/es-abstract": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", - "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", "dev": true, "dependencies": { + "array-buffer-byte-length": "^1.0.0", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", + "get-intrinsic": "^1.2.0", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -5093,8 +5089,8 @@ "has-property-descriptors": "^1.0.0", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.4", - "is-array-buffer": "^3.0.1", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", @@ -5102,11 +5098,12 @@ "is-string": "^1.0.7", "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", + "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", "typed-array-length": "^1.0.4", @@ -5136,9 +5133,9 @@ "dev": true }, "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", "dev": true }, "node_modules/es-set-tostringtag": { @@ -6218,9 +6215,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "node_modules/grapheme-breaker": { @@ -6442,9 +6439,9 @@ } }, "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -6720,9 +6717,9 @@ "dev": true }, "node_modules/htmlparser2/node_modules/readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -7076,9 +7073,9 @@ } }, "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -7651,6 +7648,22 @@ "node": ">=0.10.0" } }, + "node_modules/launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + } + }, + "node_modules/launch-editor/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -7808,9 +7821,9 @@ } }, "node_modules/memfs": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", - "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.0.tgz", + "integrity": "sha512-yK6o8xVJlQerz57kvPROwTMgx5WtGwC2ZxDtOUsnGl49rHjYkfQoPNZPCKH73VdLE1BwBu/+Fx/NL8NYMUw2aA==", "dev": true, "dependencies": { "fs-monkey": "^1.0.3" @@ -8182,9 +8195,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz", + "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==", "dev": true }, "node_modules/oauth-sign": { @@ -10134,9 +10147,9 @@ } }, "node_modules/regexpu-core": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.1.tgz", - "integrity": "sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", "dev": true, "dependencies": { "@babel/regjsgen": "^0.8.0", @@ -10393,12 +10406,12 @@ "dev": true }, "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, "dependencies": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10921,6 +10934,15 @@ "node": ">=0.10.0" } }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -11262,9 +11284,9 @@ } }, "node_modules/spdy-transport/node_modules/readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { "inherits": "^2.0.3", @@ -11536,6 +11558,23 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/string.prototype.trimend": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", @@ -11714,9 +11753,9 @@ } }, "node_modules/terser": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", - "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", + "version": "5.16.9", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", + "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.2", @@ -12517,13 +12556,13 @@ "dev": true }, "node_modules/webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", + "version": "5.79.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.79.0.tgz", + "integrity": "sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", + "@types/estree": "^1.0.0", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -12532,7 +12571,7 @@ "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -12543,7 +12582,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -12740,9 +12779,9 @@ } }, "node_modules/webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz", + "integrity": "sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==", "dev": true, "dependencies": { "@types/bonjour": "^3.5.9", @@ -12764,6 +12803,7 @@ "html-entities": "^2.3.2", "http-proxy-middleware": "^2.0.3", "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", "open": "^8.0.9", "p-retry": "^4.5.0", "rimraf": "^3.0.2", @@ -12773,7 +12813,7 @@ "sockjs": "^0.3.24", "spdy": "^4.0.2", "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" + "ws": "^8.13.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" @@ -12789,6 +12829,9 @@ "webpack": "^4.37.0 || ^5.0.0" }, "peerDependenciesMeta": { + "webpack": { + "optional": true + }, "webpack-cli": { "optional": true } @@ -12962,9 +13005,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", - "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "engines": { "node": ">=10.0.0" @@ -13214,46 +13257,46 @@ }, "dependencies": { "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, "requires": { - "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", "dev": true, "requires": { "@babel/highlight": "^7.18.6" } }, "@babel/compat-data": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", - "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", + "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", "dev": true }, "@babel/core": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", - "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", + "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.0", - "@babel/helper-compilation-targets": "^7.20.7", - "@babel/helper-module-transforms": "^7.21.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", + "@babel/helper-module-transforms": "^7.21.2", "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.0", + "@babel/parser": "^7.21.4", "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0", + "@babel/traverse": "^7.21.4", + "@babel/types": "^7.21.4", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -13276,28 +13319,15 @@ } }, "@babel/generator": { - "version": "7.21.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", - "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", + "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", "dev": true, "requires": { - "@babel/types": "^7.21.0", + "@babel/types": "^7.21.4", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } } }, "@babel/helper-annotate-as-pure": { @@ -13320,13 +13350,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", - "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", + "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.5", - "@babel/helper-validator-option": "^7.18.6", + "@babel/compat-data": "^7.21.4", + "@babel/helper-validator-option": "^7.21.0", "browserslist": "^4.21.3", "lru-cache": "^5.1.1", "semver": "^6.3.0" @@ -13341,9 +13371,9 @@ } }, "@babel/helper-create-class-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", - "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.4.tgz", + "integrity": "sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.18.6", @@ -13357,9 +13387,9 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.0.tgz", - "integrity": "sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz", + "integrity": "sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.18.6", @@ -13432,12 +13462,12 @@ } }, "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.21.4" } }, "@babel/helper-module-transforms": { @@ -13577,9 +13607,9 @@ } }, "@babel/parser": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", - "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", + "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -13807,12 +13837,12 @@ } }, "@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.21.4.tgz", + "integrity": "sha512-l9xd3N+XG4fZRxEP3vXdK6RW7vN1Uf5dxzRC/09wV86wqZ/YYQooBIGNsiRdfNR3/q2/5pPzV4B54J/9ctX5jw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-syntax-import-assertions": { @@ -13834,12 +13864,12 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.18.6" + "@babel/helper-plugin-utils": "^7.20.2" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -13980,9 +14010,9 @@ } }, "@babel/plugin-transform-destructuring": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.7.tgz", - "integrity": "sha512-Xwg403sRrZb81IVB79ZPqNQME23yhugYVqgTxAhT99h485F4f+GMELFhhOsscDUB7HCswepKeCKLn/GZvUKoBA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz", + "integrity": "sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.20.2" @@ -14138,9 +14168,9 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.7.tgz", - "integrity": "sha512-WiWBIkeHKVOSYPO0pWkxGPfKeWrCJyD3NJ53+Lrp/QMSZbsVPovrVl2aWZ19D/LTVnaDv5Ap7GJ/B2CTOZdrfA==", + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz", + "integrity": "sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.20.2" @@ -14253,31 +14283,31 @@ } }, "@babel/preset-env": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz", - "integrity": "sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.21.4.tgz", + "integrity": "sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.1", - "@babel/helper-compilation-targets": "^7.20.0", + "@babel/compat-data": "^7.21.4", + "@babel/helper-compilation-targets": "^7.21.4", "@babel/helper-plugin-utils": "^7.20.2", - "@babel/helper-validator-option": "^7.18.6", + "@babel/helper-validator-option": "^7.21.0", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.20.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.20.7", + "@babel/plugin-proposal-async-generator-functions": "^7.20.7", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", + "@babel/plugin-proposal-class-static-block": "^7.21.0", "@babel/plugin-proposal-dynamic-import": "^7.18.6", "@babel/plugin-proposal-export-namespace-from": "^7.18.9", "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", + "@babel/plugin-proposal-logical-assignment-operators": "^7.20.7", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.20.2", + "@babel/plugin-proposal-object-rest-spread": "^7.20.7", "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", + "@babel/plugin-proposal-private-property-in-object": "^7.21.0", "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -14294,40 +14324,40 @@ "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.20.7", + "@babel/plugin-transform-async-to-generator": "^7.20.7", "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.20.2", - "@babel/plugin-transform-classes": "^7.20.2", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.20.2", + "@babel/plugin-transform-block-scoping": "^7.21.0", + "@babel/plugin-transform-classes": "^7.21.0", + "@babel/plugin-transform-computed-properties": "^7.20.7", + "@babel/plugin-transform-destructuring": "^7.21.3", "@babel/plugin-transform-dotall-regex": "^7.18.6", "@babel/plugin-transform-duplicate-keys": "^7.18.9", "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", + "@babel/plugin-transform-for-of": "^7.21.0", "@babel/plugin-transform-function-name": "^7.18.9", "@babel/plugin-transform-literals": "^7.18.9", "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.19.6", - "@babel/plugin-transform-modules-commonjs": "^7.19.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.6", + "@babel/plugin-transform-modules-amd": "^7.20.11", + "@babel/plugin-transform-modules-commonjs": "^7.21.2", + "@babel/plugin-transform-modules-systemjs": "^7.20.11", "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.20.5", "@babel/plugin-transform-new-target": "^7.18.6", "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.20.1", + "@babel/plugin-transform-parameters": "^7.21.3", "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", + "@babel/plugin-transform-regenerator": "^7.20.5", "@babel/plugin-transform-reserved-words": "^7.18.6", "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", + "@babel/plugin-transform-spread": "^7.20.7", "@babel/plugin-transform-sticky-regex": "^7.18.6", "@babel/plugin-transform-template-literals": "^7.18.9", "@babel/plugin-transform-typeof-symbol": "^7.18.9", "@babel/plugin-transform-unicode-escapes": "^7.18.10", "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.20.2", + "@babel/types": "^7.21.4", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -14383,27 +14413,27 @@ } }, "@babel/traverse": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", - "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", + "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.21.1", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.4", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.21.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.2", - "@babel/types": "^7.21.2", + "@babel/parser": "^7.21.4", + "@babel/types": "^7.21.4", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", - "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", + "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.19.4", @@ -14424,13 +14454,14 @@ "dev": true }, "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" } }, "@jridgewell/resolve-uri": { @@ -14446,42 +14477,37 @@ "dev": true }, "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", + "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dev": true, "requires": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.17", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", - "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, "requires": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } } }, "@leichtgewicht/ip-codec": { @@ -14623,9 +14649,9 @@ } }, "@types/eslint": { - "version": "8.21.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", - "integrity": "sha512-rc9K8ZpVjNcLs8Fp0dkozd5Pt2Apk1glO4Vgz8ix1u6yFByxfqo5Yavpy65o+93TAe24jr7v+eSBtFLvOQtCRQ==", + "version": "8.37.0", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.37.0.tgz", + "integrity": "sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==", "dev": true, "requires": { "@types/estree": "*", @@ -14643,9 +14669,9 @@ } }, "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz", + "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", "dev": true }, "@types/express": { @@ -14699,9 +14725,9 @@ "dev": true }, "@types/node": { - "version": "18.15.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.0.tgz", - "integrity": "sha512-z6nr0TTEOBGkzLGmbypWOGnpSpSIBorEhC4L+4HeQ2iezKCi4f77kyslRwvHeNitymGQ+oFyIWGP96l/DPSV9w==", + "version": "18.15.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", + "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", "dev": true }, "@types/q": { @@ -15245,6 +15271,16 @@ "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", "dev": true }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", @@ -15606,9 +15642,9 @@ } }, "bonjour-service": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.0.tgz", - "integrity": "sha512-LVRinRB3k1/K0XzZ2p58COnWvkQknIY6sf0zF2rpErvcJXpMBttEPQSxK+HEXSS9VmpZlDoDnQWv8ftJT20B0Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", "dev": true, "requires": { "array-flatten": "^2.1.2", @@ -15731,9 +15767,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -15885,9 +15921,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001464", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001464.tgz", - "integrity": "sha512-oww27MtUmusatpRpCGSOneQk2/l5czXANDSFvsc7VuOQ86s3ANhZetpwXNf1zY/zdfP63Xvjz325DAdAoES13g==", + "version": "1.0.30001478", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001478.tgz", + "integrity": "sha512-gMhDyXGItTHipJj2ApIvR+iVB5hd0KP3svMWWXDvZOmjzJJassGLMfxRkQCSYgGd2gtdL/ReeiyvMSFD1Ss6Mw==", "dev": true }, "caseless": { @@ -16397,9 +16433,9 @@ "dev": true }, "core-js-compat": { - "version": "3.29.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.29.0.tgz", - "integrity": "sha512-ScMn3uZNAFhK2DGoEfErguoiAHhV2Ju+oJo/jK08p7B3f3UhocUrCCkTvnZaiS+edl5nlIoiBXKcwMc6elv4KQ==", + "version": "3.30.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.30.1.tgz", + "integrity": "sha512-d690npR7MC6P0gq4npTl5n2VQeNAmUrJ90n+MHiKS7W2+xno4o3F5GDEuylSdi6EJ3VssibSGXOa1r3YXD3Mhw==", "dev": true, "requires": { "browserslist": "^4.21.5" @@ -16998,9 +17034,9 @@ "dev": true }, "dns-packet": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.4.0.tgz", - "integrity": "sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.5.0.tgz", + "integrity": "sha512-USawdAUzRkV6xrqTjiAEp6M9YagZEzWcSUaZTcIFAiyQWW1SoI6KyId8y2+/71wbgHKQAKd+iupLv4YvEwYWvA==", "dev": true, "requires": { "@leichtgewicht/ip-codec": "^2.0.1" @@ -17130,9 +17166,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.327", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.327.tgz", - "integrity": "sha512-DIk2H4g/3ZhjgiABJjVdQvUdMlSABOsjeCm6gmUzIdKxAuFrGiJ8QXMm3i09grZdDBMC/d8MELMrdwYRC0+YHg==", + "version": "1.4.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.363.tgz", + "integrity": "sha512-ReX5qgmSU7ybhzMuMdlJAdYnRhT90UB3k9M05O5nF5WH3wR5wgdJjXw0uDeFyKNhmglmQiOxkAbzrP0hMKM59g==", "dev": true }, "elliptic": { @@ -17196,18 +17232,18 @@ } }, "es-abstract": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", - "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "version": "1.21.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", + "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", "dev": true, "requires": { + "array-buffer-byte-length": "^1.0.0", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", + "get-intrinsic": "^1.2.0", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -17215,8 +17251,8 @@ "has-property-descriptors": "^1.0.0", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", - "internal-slot": "^1.0.4", - "is-array-buffer": "^3.0.1", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", "is-callable": "^1.2.7", "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", @@ -17224,11 +17260,12 @@ "is-string": "^1.0.7", "is-typed-array": "^1.1.10", "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", + "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", "regexp.prototype.flags": "^1.4.3", "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", "string.prototype.trimend": "^1.0.6", "string.prototype.trimstart": "^1.0.6", "typed-array-length": "^1.0.4", @@ -17251,9 +17288,9 @@ "dev": true }, "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.2.1.tgz", + "integrity": "sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==", "dev": true }, "es-set-tostringtag": { @@ -18085,9 +18122,9 @@ } }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, "grapheme-breaker": { @@ -18254,9 +18291,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -18480,9 +18517,9 @@ "dev": true }, "readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -18743,9 +18780,9 @@ } }, "is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz", + "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==", "dev": true, "requires": { "has": "^1.0.3" @@ -19163,6 +19200,24 @@ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, + "launch-editor": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz", + "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "shell-quote": "^1.7.3" + }, + "dependencies": { + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + } + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -19299,9 +19354,9 @@ "dev": true }, "memfs": { - "version": "3.4.13", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.13.tgz", - "integrity": "sha512-omTM41g3Skpvx5dSYeZIbXKcXoAVc/AoMNwn9TKx++L/gaen/+4TTttmu8ZSch5vfVJ8uJvGbroTsIlslRg6lg==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.0.tgz", + "integrity": "sha512-yK6o8xVJlQerz57kvPROwTMgx5WtGwC2ZxDtOUsnGl49rHjYkfQoPNZPCKH73VdLE1BwBu/+Fx/NL8NYMUw2aA==", "dev": true, "requires": { "fs-monkey": "^1.0.3" @@ -19607,9 +19662,9 @@ } }, "nwsapi": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", - "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.4.tgz", + "integrity": "sha512-NHj4rzRo0tQdijE9ZqAx6kYDcoRwYwSYzCA8MY3JzfxlrvEU0jhnhJT9BhqhJs7I/dKcrDm6TyulaRqZPIhN5g==", "dev": true }, "oauth-sign": { @@ -21200,9 +21255,9 @@ } }, "regexpu-core": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.1.tgz", - "integrity": "sha512-nCOzW2V/X15XpLsK2rlgdwrysrBq+AauCn+omItIz4R1pIcmeot5zvjdmOBRLzEH/CkC6IxMJVmxDe3QcMuNVQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", "dev": true, "requires": { "@babel/regjsgen": "^0.8.0", @@ -21394,12 +21449,12 @@ "dev": true }, "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", "dev": true, "requires": { - "is-core-module": "^2.9.0", + "is-core-module": "^2.11.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" } @@ -21810,6 +21865,12 @@ "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true + }, "side-channel": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -22102,9 +22163,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", - "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -22326,6 +22387,17 @@ "safe-buffer": "~5.2.0" } }, + "string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, "string.prototype.trimend": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", @@ -22465,9 +22537,9 @@ "dev": true }, "terser": { - "version": "5.16.6", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.6.tgz", - "integrity": "sha512-IBZ+ZQIA9sMaXmRZCUMDjNH0D5AQQfdn4WUjHL0+1lF4TP1IHRJbrhb6fNaXWikrYQTSkb7SLxkeXAiy1p7mbg==", + "version": "5.16.9", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.9.tgz", + "integrity": "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.2", @@ -23115,13 +23187,13 @@ "dev": true }, "webpack": { - "version": "5.76.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.76.0.tgz", - "integrity": "sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==", + "version": "5.79.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.79.0.tgz", + "integrity": "sha512-3mN4rR2Xq+INd6NnYuL9RC9GAmc1ROPKJoHhrZ4pAjdMFEkJJWrsPw8o2JjCIyQyTu7rTXYn4VG6OpyB3CobZg==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", + "@types/estree": "^1.0.0", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", @@ -23130,7 +23202,7 @@ "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", + "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -23141,7 +23213,7 @@ "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", + "terser-webpack-plugin": "^5.3.7", "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, @@ -23276,9 +23348,9 @@ } }, "webpack-dev-server": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz", - "integrity": "sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==", + "version": "4.13.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.13.2.tgz", + "integrity": "sha512-5i6TrGBRxG4vnfDpB6qSQGfnB6skGBXNL5/542w2uRGLimX6qeE5BQMLrzIC3JYV/xlGOv+s+hTleI9AZKUQNw==", "dev": true, "requires": { "@types/bonjour": "^3.5.9", @@ -23300,6 +23372,7 @@ "html-entities": "^2.3.2", "http-proxy-middleware": "^2.0.3", "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", "open": "^8.0.9", "p-retry": "^4.5.0", "rimraf": "^3.0.2", @@ -23309,7 +23382,7 @@ "sockjs": "^0.3.24", "spdy": "^4.0.2", "webpack-dev-middleware": "^5.3.1", - "ws": "^8.4.2" + "ws": "^8.13.0" }, "dependencies": { "ajv": { @@ -23427,9 +23500,9 @@ } }, "ws": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", - "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", + "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", "dev": true, "requires": {} } diff --git a/lib/wasi-web/public/worker.js b/lib/wasi-web/public/worker.js index 31dbda803b2..664696d0fbf 100644 --- a/lib/wasi-web/public/worker.js +++ b/lib/wasi-web/public/worker.js @@ -1,61 +1,49 @@ +import { schedule_wasm_task } from '../../..'; + // The purpose of this file is two-fold: // First: Expose a function to start a web worker. This function must // not be inlined into the Rust lib, as otherwise bundlers could not // bundle it -- huh. export function startWorker(module, memory, state, opts, helper) { - const worker = new Worker(new URL('./worker.js', - import.meta.url), opts); - try { + const worker = new Worker(new URL('./worker.js', + import.meta.url), opts); + + // When the worker wants to schedule some work it will + // post a message back to the main thread + worker.onmessage = async ev => { + let [task, module, memory] = ev.data; + schedule_wasm_task(task, module, memory); + }; worker.postMessage([module, memory, state, helper.mainJS()]); - } catch(err) { + } catch (err) { return new Promise((res, rej) => { rej(err); }); } - return new Promise((res, rej) => { - worker.onmessage = async ev => { - if (ev.data === 'started') res(); - else { - const importFrom = (typeof __webpack_require__ === 'function') ? import('../../..') : import(mainJS); - const { - worker_schedule_task, - } = await importFrom; - - let [module, memory, state] = event.data; - worker_schedule_task(module, memory, state); - } - }; - worker.onerror = rej; + res(); }); } export function startWasm(module, memory, ctx, opts, helper, wasm_module, wasm_memory) { - const worker = new Worker(new URL('./worker.js', - import.meta.url), opts); - try { + const worker = new Worker(new URL('./worker.js', + import.meta.url), opts); + + // When the worker wants to schedule some work it will + // post a message back to the main thread + worker.onmessage = async ev => { + let [task, module, memory] = ev.data; + schedule_wasm_task(task, module, memory); + }; worker.postMessage([module, memory, ctx, helper.mainJS(), wasm_module, wasm_memory]); - } catch(err) { + } catch (err) { return new Promise((res, rej) => { rej(err); }); } - return new Promise((res, rej) => { - worker.onmessage = async ev => { - if (ev.data === 'started') res(); - else { - const importFrom = (typeof __webpack_require__ === 'function') ? import('../../..') : import(mainJS); - const { - worker_schedule_task, - } = await importFrom; - - let [module, memory, state] = event.data; - worker_schedule_task(module, memory, state); - } - }; - worker.onerror = rej; + res(); }); } export function scheduleTask(task, module, memory) { @@ -73,7 +61,7 @@ if (isWorker()) { // Initialize wasm module, and memory. `state` is the shared state, // to be used with `worker_entry_point`. - self.onmessage = async event => { + self.onmessage = async ev => { // This crate only works with bundling via webpack or not // using a bundler at all: // When bundling with webpack, this file is relative to the wasm @@ -82,36 +70,26 @@ if (isWorker()) { // When using it without any bundlers, the module that // provided the `helper` object below is loaded; in other words // the main wasm module. - if (event.data.length == 4) { - let [module, memory, state, mainJS] = event.data; + if (ev.data.length == 4) { + let [module, memory, state, mainJS] = ev.data; const importFrom = (typeof __webpack_require__ === 'function') ? import('../../..') : import(mainJS); - try { - const { - default: init, - worker_entry_point, - worker_entry_point_with_val - } = await importFrom; - await init(module, memory); + const { + default: init, + worker_entry_point, + } = await importFrom; + await init(module, memory); - worker_entry_point(state); - postMessage('started'); - // There shouldn't be any additional messages after the first. - self.onmessage = event => { - console.error("Unexpected message", event); - } - } catch (err) { - // Propagate to main `onerror`: - setTimeout(() => { - throw err; - //Terminate the worker - close(); - }); - throw err; - } + worker_entry_point(state); } else { - let [module, memory, ctx, mainJS, wasm_module, wasm_memory] = event.data; - const importFrom = (typeof __webpack_require__ === 'function') ? import('../../..') : import(mainJS); try { + // There shouldn't be any additional messages after the first so we + // need to unhook it + self.onmessage = ev => { + console.error("wasm threads can only run a single process then exit", ev); + } + + let [module, memory, ctx, mainJS, wasm_module, wasm_memory] = ev.data; + const importFrom = (typeof __webpack_require__ === 'function') ? import('../../..') : import(mainJS); const { default: init, wasm_entry_point, @@ -119,19 +97,9 @@ if (isWorker()) { await init(module, memory); wasm_entry_point(ctx, wasm_module, wasm_memory); - postMessage('started'); - // There shouldn't be any additional messages after the first. - self.onmessage = event => { - console.error("Unexpected message", event); - } - } catch (err) { - // Propagate to main `onerror`: - setTimeout(() => { - throw err; - //Terminate the worker - close(); - }); - throw err; + } finally { + //Terminate the worker + close(); } } } diff --git a/lib/wasi-web/src/glue.rs b/lib/wasi-web/src/glue.rs index 0cab4ef5149..52ce3b30a63 100644 --- a/lib/wasi-web/src/glue.rs +++ b/lib/wasi-web/src/glue.rs @@ -10,6 +10,7 @@ use tracing::{debug, error, info, trace, warn}; use wasm_bindgen::{prelude::*, JsCast}; use wasmer_wasix::{ bin_factory::ModuleCache, + capabilities::Capabilities, os::{Console, InputEvent, Tty, TtyOptions}, Pipe, }; @@ -88,8 +89,9 @@ pub fn start() -> Result<(), JsValue> { let (term_tx, mut term_rx) = mpsc::unbounded_channel(); { - let terminal: Terminal = terminal.clone().dyn_into().unwrap(); + let terminal = terminal.clone(); wasm_bindgen_futures::spawn_local(async move { + let terminal: Terminal = terminal.dyn_into().unwrap(); while let Some(cmd) = term_rx.recv().await { match cmd { TerminalCommandRx::Print(text) => { @@ -188,6 +190,11 @@ pub fn start() -> Result<(), JsValue> { .with_stderr(Box::new(stderr)) .with_env(env); + let mut capabilities = Capabilities::default(); + capabilities.threading.max_threads = Some(50); + capabilities.threading.enable_asynchronous_threading = true; + console = console.with_capabilities(capabilities); + let (tx, mut rx) = mpsc::unbounded_channel(); let tx_key = tx.clone(); diff --git a/lib/wasi-web/src/pool.rs b/lib/wasi-web/src/pool.rs index 5542bc15203..0987dc462cd 100644 --- a/lib/wasi-web/src/pool.rs +++ b/lib/wasi-web/src/pool.rs @@ -27,10 +27,18 @@ use tokio::{ use tracing::{debug, error, info, trace, warn}; use wasm_bindgen::{prelude::*, JsCast}; use wasm_bindgen_futures::JsFuture; +use wasmer::AsStoreRef; use wasmer_wasix::{ - runtime::SpawnType, + capture_snapshot, + runtime::{ + task_manager::{ + TaskWasm, TaskWasmRun, TaskWasmRunProperties, WasmResumeTask, WasmResumeTrigger, + }, + SpawnMemoryType, + }, wasmer::{AsJs, Memory, MemoryType, Module, Store, WASM_MAX_PAGES}, - VirtualTaskManager, WasiThreadError, + wasmer_wasix_types::wasi::Errno, + InstanceSnapshot, VirtualTaskManager, WasiEnv, WasiFunctionEnv, WasiThreadError, }; use web_sys::{DedicatedWorkerGlobalScope, WorkerOptions, WorkerType}; use xterm_js_rs::Terminal; @@ -44,30 +52,34 @@ pub type BoxRunAsync<'a, T> = Box Pin + 'static>> + Send + 'a>; #[derive(Debug, Clone, Copy)] -enum WasmRunType { - Create, - CreateWithMemory(MemoryType), - Existing(MemoryType), +enum WasmMemoryType { + CreateMemory, + CreateMemoryOfType(MemoryType), + CopyMemory(MemoryType), + ShareMemory(MemoryType), } #[derive(Derivative)] #[derivative(Debug)] -struct WasmRunCommand { +struct WasmRunTrigger { #[derivative(Debug = "ignore")] - run: Box) + Send + 'static>, - ty: WasmRunType, - store: Store, - module_bytes: Bytes, -} - -enum WasmRunMemory { - WithoutMemory, - WithMemory(MemoryType), + run: Box, + memory_ty: MemoryType, + env: WasiEnv, } -struct WasmRunContext { - cmd: WasmRunCommand, - memory: WasmRunMemory, +#[derive(Derivative)] +#[derivative(Debug)] +struct WasmRunCommand { + #[derivative(Debug = "ignore")] + run: Box, + run_type: WasmMemoryType, + env: WasiEnv, + module_bytes: Bytes, + snapshot: Option, + trigger: Option, + update_layout: bool, + result: Result<(), Errno>, } trait AssertSendSync: Send + Sync {} @@ -172,7 +184,7 @@ impl LoaderHelper { extern "C" { #[wasm_bindgen(js_name = "startWorker")] fn start_worker( - module: JsValue, + module: js_sys::WebAssembly::Module, memory: JsValue, shared_data: JsValue, opts: WorkerOptions, @@ -181,17 +193,17 @@ extern "C" { #[wasm_bindgen(js_name = "startWasm")] fn start_wasm( - module: JsValue, + module: js_sys::WebAssembly::Module, memory: JsValue, ctx: JsValue, opts: WorkerOptions, builder: LoaderHelper, - wasm_module: JsValue, + wasm_module: js_sys::WebAssembly::Module, wasm_memory: JsValue, ) -> Promise; #[wasm_bindgen(js_name = "scheduleTask")] - fn schedule_task(task: JsValue, module: JsValue, memory: JsValue); + fn schedule_task(task: JsValue, module: js_sys::WebAssembly::Module, memory: JsValue); } impl WebThreadPool { @@ -267,38 +279,55 @@ impl WebThreadPool { self.inner.pool_reactors.spawn(Message::RunAsync(task)); } - pub fn spawn_wasm( - &self, - run: impl FnOnce(Store, Module, Option) + Send + 'static, - wasm_store: Store, - wasm_module: Module, - spawn_type: SpawnType, - ) -> Result<(), WasiThreadError> { - let mut wasm_memory = JsValue::null(); - let run_type = match spawn_type { - SpawnType::Create => WasmRunType::Create, - SpawnType::CreateWithType(mem) => WasmRunType::CreateWithMemory(mem.ty), - SpawnType::NewThread(memory) => { - wasm_memory = memory.as_jsvalue(&wasm_store); - WasmRunType::Existing(memory.ty(&wasm_store)) + pub fn spawn_wasm(&self, task: TaskWasm) -> Result<(), WasiThreadError> { + let run = task.run; + let env = task.env; + let module = task.module; + let module_bytes = module.serialize().unwrap(); + let snapshot = task.snapshot.map(|s| s.clone()); + let trigger = task.trigger; + let update_layout = task.update_layout; + + let mut memory_ty = None; + let mut memory = JsValue::null(); + let run_type = match task.spawn_type { + SpawnMemoryType::CreateMemory => WasmMemoryType::CreateMemory, + SpawnMemoryType::CreateMemoryOfType(ty) => { + memory_ty = Some(ty.clone()); + WasmMemoryType::CreateMemoryOfType(ty) + } + SpawnMemoryType::CopyMemory(m, store) => { + memory_ty = Some(m.ty(&store)); + memory = m.as_jsvalue(&store); + WasmMemoryType::CopyMemory(m.ty(&store)) + } + SpawnMemoryType::ShareMemory(m, store) => { + memory_ty = Some(m.ty(&store)); + memory = m.as_jsvalue(&store); + WasmMemoryType::ShareMemory(m.ty(&store)) } }; let task = Box::new(WasmRunCommand { - run: Box::new(move |store, module, memory| { - run(store, module, memory); + trigger: trigger.map(|trigger| WasmRunTrigger { + run: trigger, + memory_ty: memory_ty.expect("triggers must have the a known memory type"), + env: env.clone(), }), - ty: run_type, - store: wasm_store, - module_bytes: wasm_module.serialize().unwrap(), + run, + run_type, + env, + module_bytes, + snapshot, + update_layout, + result: Ok(()), }); let task = Box::into_raw(task); - schedule_task( - JsValue::from(task as u32), - JsValue::from(wasm_module), - wasm_memory, - ); + let module = JsValue::from(module) + .dyn_into::() + .unwrap(); + schedule_task(JsValue::from(task as u32), module, memory); Ok(()) } @@ -311,6 +340,55 @@ impl WebThreadPool { } } +fn _build_ctx_and_store( + module: js_sys::WebAssembly::Module, + memory: JsValue, + module_bytes: Bytes, + env: WasiEnv, + run_type: WasmMemoryType, + snapshot: Option, + update_layout: bool, +) -> Option<(WasiFunctionEnv, Store)> { + // Compile the web assembly module + let module: Module = (module, module_bytes).into(); + + // Make a fake store which will hold the memory we just transferred + let mut temp_store = env.runtime().new_store(); + let spawn_type = match run_type { + WasmMemoryType::CreateMemory => SpawnMemoryType::CreateMemory, + WasmMemoryType::CreateMemoryOfType(mem) => SpawnMemoryType::CreateMemoryOfType(mem), + WasmMemoryType::CopyMemory(ty) | WasmMemoryType::ShareMemory(ty) => { + let memory = match Memory::from_jsvalue(&mut temp_store, &ty, &memory) { + Ok(a) => a, + Err(_) => { + error!("Failed to receive memory for module"); + return None; + } + }; + match run_type { + WasmMemoryType::CopyMemory(_) => { + SpawnMemoryType::CopyMemory(memory, temp_store.as_store_ref()) + } + WasmMemoryType::ShareMemory(_) => { + SpawnMemoryType::ShareMemory(memory, temp_store.as_store_ref()) + } + _ => unreachable!(), + } + } + }; + + let snapshot = snapshot.as_ref(); + let (ctx, store) = + match WasiFunctionEnv::new_with_store(module, env, snapshot, spawn_type, update_layout) { + Ok(a) => a, + Err(err) => { + error!("Failed to crate wasi context - {}", err); + return None; + } + }; + Some((ctx, store)) +} + async fn _compile_module(bytes: &[u8]) -> Result { let js_bytes = unsafe { Uint8Array::view(bytes) }; Ok( @@ -377,7 +455,9 @@ impl PoolState { let ptr = Arc::into_raw(state); let result = wasm_bindgen_futures::JsFuture::from(start_worker( - wasm_bindgen::module(), + wasm_bindgen::module() + .dyn_into::() + .unwrap(), wasm_bindgen::memory(), JsValue::from(ptr as u32), opts, @@ -558,139 +638,70 @@ pub fn worker_entry_point(state_ptr: u32) { } #[wasm_bindgen(skip_typescript)] -pub fn wasm_entry_point(ctx_ptr: u32, wasm_module: JsValue, wasm_memory: JsValue) { +pub fn wasm_entry_point( + task_ptr: u32, + wasm_module: js_sys::WebAssembly::Module, + wasm_memory: JsValue, +) { // Grab the run wrapper that passes us the rust variables (and extract the callback) - let ctx = ctx_ptr as *mut WasmRunContext; - let ctx = unsafe { Box::from_raw(ctx) }; - let run_callback = (*ctx).cmd.run; - - // Compile the web assembly module - let mut wasm_store = ctx.cmd.store; - let wasm_module = match wasm_module.dyn_into::() { - Ok(a) => a, - Err(err) => { - error!( - "Failed to receive module - {}", - err.as_string().unwrap_or_else(|| format!("{:?}", err)) - ); - return; - } - }; - let wasm_module: Module = (wasm_module, ctx.cmd.module_bytes.clone()).into(); - - // If memory was passed to the web worker then construct it - let wasm_memory = match ctx.memory { - WasmRunMemory::WithoutMemory => None, - WasmRunMemory::WithMemory(wasm_memory_type) => { - let wasm_memory = - match Memory::from_jsvalue(&mut wasm_store, &wasm_memory_type, &wasm_memory) { - Ok(a) => a, - Err(err) => { - // error!( - // "Failed to receive memory for module - {}", - // err.as_string().unwrap_or_else(|| format!("{:?}", err)) - // ); - return; - } - }; - Some(wasm_memory) - } - }; - - let name = js_sys::global() - .unchecked_into::() - .name(); - debug!("{}: Entry", name); + let task = task_ptr as *mut WasmRunCommand; + let task = unsafe { Box::from_raw(task) }; + let run = (*task).run; // Invoke the callback which will run the web assembly module - run_callback(wasm_store, wasm_module, wasm_memory); + if let Some((ctx, store)) = _build_ctx_and_store( + wasm_module, + wasm_memory, + task.module_bytes, + task.env, + task.run_type, + task.snapshot, + task.update_layout, + ) { + run(TaskWasmRunProperties { + ctx, + store, + result: task.result, + }); + }; } #[wasm_bindgen()] -pub fn worker_schedule_task(task_ptr: u32, wasm_module: JsValue, mut wasm_memory: JsValue) { - // Grab the task that passes us the rust variables +pub fn schedule_wasm_task( + task_ptr: u32, + wasm_module: js_sys::WebAssembly::Module, + wasm_memory: JsValue, +) { + // Grab the run wrapper that passes us the rust variables let task = task_ptr as *mut WasmRunCommand; let mut task = unsafe { Box::from_raw(task) }; + // We will pass it on now + let trigger = task.trigger.take(); + + // We will now spawn the process in its own thread let mut opts = WorkerOptions::new(); opts.type_(WorkerType::Module); - opts.name(&*format!("WasmWorker")); + opts.name(&*format!("Wasm-Thread")); - let result = match task.ty.clone() { - WasmRunType::Create => { - let ctx = WasmRunContext { - cmd: *task, - memory: WasmRunMemory::WithoutMemory, - }; - let ctx = Box::into_raw(Box::new(ctx)); - - wasm_bindgen_futures::JsFuture::from(start_wasm( - wasm_bindgen::module(), - wasm_bindgen::memory(), - JsValue::from(ctx as u32), - opts, - LoaderHelper {}, - wasm_module, - wasm_memory, - )) + wasm_bindgen_futures::spawn_local(async move { + if let Some(trigger) = trigger { + let run = trigger.run; + task.result = run().await; } - WasmRunType::CreateWithMemory(ty) => { - if ty.shared == false { - // We can only pass memory around between web workers when its a shared memory - error!("Failed to create WASM process with external memory as only shared memory is supported yet this web assembly binary imports non-shared memory."); - return; - } - if ty.maximum.is_none() { - // Browsers require maximum number defined on shared memory - error!("Failed to create WASM process with external memory as shared memory must have a maximum size however this web assembly binary imports shared memory with no maximum defined."); - return; - } - if wasm_memory.is_null() { - let memory = match Memory::new(&mut task.store, ty.clone()) { - Ok(a) => a, - Err(err) => { - error!("Failed to create WASM memory - {}", err); - return; - } - }; - wasm_memory = memory.as_jsvalue(&task.store); - } - - let ctx = WasmRunContext { - cmd: *task, - memory: WasmRunMemory::WithMemory(ty), - }; - let ctx = Box::into_raw(Box::new(ctx)); - - wasm_bindgen_futures::JsFuture::from(start_wasm( - wasm_bindgen::module(), - wasm_bindgen::memory(), - JsValue::from(ctx as u32), - opts, - LoaderHelper {}, - wasm_module, - wasm_memory, - )) - } - WasmRunType::Existing(wasm_memory_type) => { - let ctx = WasmRunContext { - cmd: *task, - memory: WasmRunMemory::WithMemory(wasm_memory_type), - }; - let ctx = Box::into_raw(Box::new(ctx)); - - wasm_bindgen_futures::JsFuture::from(start_wasm( - wasm_bindgen::module(), - wasm_bindgen::memory(), - JsValue::from(ctx as u32), - opts, - LoaderHelper {}, - wasm_module, - wasm_memory, - )) - } - }; - - wasm_bindgen_futures::spawn_local(async move { _process_worker_result(result, None).await }); + let task = Box::into_raw(task); + let result = wasm_bindgen_futures::JsFuture::from(start_wasm( + wasm_bindgen::module() + .dyn_into::() + .unwrap(), + wasm_bindgen::memory(), + JsValue::from(task as u32), + opts, + LoaderHelper {}, + wasm_module, + wasm_memory, + )); + _process_worker_result(result, None).await + }); } diff --git a/lib/wasi-web/src/runtime.rs b/lib/wasi-web/src/runtime.rs index df70f2a763b..332782d08cd 100644 --- a/lib/wasi-web/src/runtime.rs +++ b/lib/wasi-web/src/runtime.rs @@ -19,8 +19,7 @@ use wasm_bindgen_futures::JsFuture; use wasmer_wasix::{ http::{DynHttpClient, HttpRequest, HttpResponse}, os::{TtyBridge, TtyOptions}, - runtime::SpawnType, - wasmer::{Memory, MemoryType, Module, Store, StoreMut}, + runtime::task_manager::TaskWasm, VirtualFile, VirtualNetworking, VirtualTaskManager, WasiRuntime, WasiThreadError, WasiTtyState, }; use web_sys::WebGl2RenderingContext; @@ -103,33 +102,13 @@ impl<'g> Drop for WebRuntimeGuard<'g> { #[async_trait::async_trait] #[allow(unused_variables)] impl VirtualTaskManager for WebTaskManager { - /// Build a new Webassembly memory. - /// - /// May return `None` if the memory can just be auto-constructed. - fn build_memory( - &self, - mut store: &mut StoreMut, - spawn_type: SpawnType, - ) -> Result, WasiThreadError> { - match spawn_type { - SpawnType::CreateWithType(mut mem) => { - mem.ty.shared = true; - Memory::new(&mut store, mem.ty) - .map_err(|err| { - tracing::error!("could not create memory: {err}"); - WasiThreadError::MemoryCreateFailed - }) - .map(Some) - } - SpawnType::NewThread(mem) => Ok(Some(mem)), - SpawnType::Create => Ok(None), - } - } - /// Invokes whenever a WASM thread goes idle. In some runtimes (like singlethreaded /// execution environments) they will need to do asynchronous work whenever the main /// thread goes idle and this is the place to hook for that. - async fn sleep_now(&self, time: Duration) { + fn sleep_now( + &self, + time: Duration, + ) -> Pin + Send + Sync + 'static>> { // The async code itself has to be sent to a main JS thread as this is where // time can be handled properly - later we can look at running a JS runtime // on the dedicated threads but that will require that processes can be unwound @@ -137,13 +116,20 @@ impl VirtualTaskManager for WebTaskManager { let (tx, rx) = tokio::sync::oneshot::channel(); self.pool.spawn_shared(Box::new(move || { Box::pin(async move { - let promise = bindgen_sleep(time.as_millis() as i32); + let time = if time.as_millis() < i32::MAX as u128 { + time.as_millis() as i32 + } else { + i32::MAX + }; + let promise = bindgen_sleep(time); let js_fut = JsFuture::from(promise); let _ = js_fut.await; let _ = tx.send(()); }) })); - let _ = rx.await; + Box::pin(async move { + let _ = rx.await; + }) } /// Starts an asynchronous task that will run on a shared worker pool @@ -179,14 +165,8 @@ impl VirtualTaskManager for WebTaskManager { /// Starts an asynchronous task will will run on a dedicated thread /// pulled from the worker pool that has a stateful thread local variable /// It is ok for this task to block execution and any async futures within its scope - fn task_wasm( - &self, - task: Box) + Send + 'static>, - store: Store, - module: Module, - spawn_type: SpawnType, - ) -> Result<(), WasiThreadError> { - self.pool.spawn_wasm(task, store, module, spawn_type)?; + fn task_wasm(&self, task: TaskWasm) -> Result<(), WasiThreadError> { + self.pool.spawn_wasm(task)?; Ok(()) } diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index a2986cb196e..1cf3c6656f9 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -2,19 +2,17 @@ use std::{pin::Pin, sync::Arc}; use crate::{ os::task::{thread::WasiThreadRunGuard, TaskJoinHandle}, - runtime::module_cache::ModuleCache, - VirtualBusError, WasiRuntimeError, + runtime::task_manager::{TaskWasm, TaskWasmRunProperties}, + syscalls::rewind, + RewindState, VirtualBusError, WasiError, WasiRuntimeError, }; use futures::Future; use tracing::*; -use wasmer::{FunctionEnvMut, Instance, Memory, Module, Store}; +use wasmer::{Function, FunctionEnvMut, Memory32, Memory64, Module, Store}; use wasmer_wasix_types::wasi::Errno; use super::{BinFactory, BinaryPackage}; -use crate::{ - import_object_for_all_wasi_versions, runtime::SpawnType, SpawnedMemory, WasiEnv, - WasiFunctionEnv, WasiRuntime, -}; +use crate::{runtime::SpawnMemoryType, WasiEnv, WasiFunctionEnv, WasiRuntime}; #[tracing::instrument(level = "trace", skip_all, fields(%name, %binary.package_name))] pub async fn spawn_exec( @@ -68,12 +66,11 @@ pub async fn spawn_exec( tracing::debug!("{:?}", env.state.fs); // Now run the module - spawn_exec_module(module, store, env, runtime) + spawn_exec_module(module, env, runtime) } pub fn spawn_exec_module( module: Module, - store: Store, env: WasiEnv, runtime: &Arc, ) -> Result { @@ -90,103 +87,54 @@ pub fn spawn_exec_module( // Determine if we are going to create memory and import it or just rely on self creation of memory let memory_spawn = match shared_memory { - Some(ty) => SpawnType::CreateWithType(SpawnedMemory { ty }), - None => SpawnType::Create, + Some(ty) => SpawnMemoryType::CreateMemoryOfType(ty), + None => SpawnMemoryType::CreateMemory, }; // Create a thread that will run this process - let runtime = runtime.clone(); let tasks_outer = tasks.clone(); - let task = { - move |mut store, module, memory: Option| { - // Create the WasiFunctionEnv - let mut wasi_env = env; - wasi_env.runtime = runtime; - let thread = WasiThreadRunGuard::new(wasi_env.thread.clone()); - - let mut wasi_env = WasiFunctionEnv::new(&mut store, wasi_env); - - // Let's instantiate the module with the imports. - let (mut import_object, init) = - import_object_for_all_wasi_versions(&module, &mut store, &wasi_env.env); - let imported_memory = if let Some(memory) = memory { - import_object.define("env", "memory", memory.clone()); - Some(memory) - } else { - None - }; + let run = { + move |props: TaskWasmRunProperties| { + let ctx = props.ctx; + let mut store = props.store; - let instance = match Instance::new(&mut store, &module, &import_object) { - Ok(a) => a, - Err(err) => { - error!("wasi[{}]::wasm instantiate error ({})", pid, err); - wasi_env - .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); - return; - } - }; - - init(&instance, &store).unwrap(); + // Create the WasiFunctionEnv + let thread = WasiThreadRunGuard::new(ctx.data(&store).thread.clone()); - // Initialize the WASI environment - if let Err(err) = - wasi_env.initialize_with_memory(&mut store, instance.clone(), imported_memory) - { - error!("wasi[{}]::wasi initialize error ({})", pid, err); - wasi_env + // Perform the initialization + let ctx = { + // If this module exports an _initialize function, run that first. + if let Ok(initialize) = ctx .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); - return; - } - - // If this module exports an _initialize function, run that first. - if let Ok(initialize) = instance.exports.get_function("_initialize") { - if let Err(err) = initialize.call(&mut store, &[]) { - thread.thread.set_status_finished(Err(err.into())); - wasi_env - .data(&store) - .blocking_cleanup(Some(Errno::Noexec.into())); - return; + .inner() + .instance + .exports + .get_function("_initialize") + { + let initialize = initialize.clone(); + if let Err(err) = initialize.call(&mut store, &[]) { + thread.thread.set_status_finished(Err(err.into())); + ctx.data(&store) + .blocking_cleanup(Some(Errno::Noexec.into())); + return; + } } - } - // Let's call the `_start` function, which is our `main` function in Rust. - let start = instance.exports.get_function("_start").ok(); + WasiFunctionEnv { env: ctx.env } + }; // If there is a start function debug!("wasi[{}]::called main()", pid); // TODO: rewrite to use crate::run_wasi_func - thread.thread.set_status_running(); - - let ret = if let Some(start) = start { - start - .call(&mut store, &[]) - .map_err(WasiRuntimeError::from) - .map(|_| Errno::Success) - } else { - debug!("wasi[{}]::exec-failed: missing _start function", pid); - Ok(Errno::Noexec) - }; - - let code = if let Err(err) = &ret { - err.as_exit_code().unwrap_or_else(|| Errno::Noexec.into()) - } else { - Errno::Success.into() - }; - - // Cleanup the environment - wasi_env.data(&store).blocking_cleanup(Some(code)); - - debug!("wasi[{pid}]::main() has exited with {code}"); - thread.thread.set_status_finished(ret.map(|a| a.into())); + // Call the module + call_module(ctx, store, thread, None); } }; tasks_outer - .task_wasm(Box::new(task), store, module, memory_spawn) + .task_wasm(TaskWasm::new(Box::new(run), env, module, true).with_memory(memory_spawn)) .map_err(|err| { error!("wasi[{}]::failed to launch module - {}", pid, err); VirtualBusError::UnknownError @@ -196,6 +144,130 @@ pub fn spawn_exec_module( Ok(join_handle) } +fn get_start(ctx: &WasiFunctionEnv, store: &Store) -> Option { + ctx.data(store) + .inner() + .instance + .exports + .get_function("_start") + .map(|a| a.clone()) + .ok() +} + +/// Calls the module +fn call_module( + ctx: WasiFunctionEnv, + mut store: Store, + handle: WasiThreadRunGuard, + rewind_state: Option<(RewindState, Result<(), Errno>)>, +) { + let env = ctx.data(&store); + let pid = env.pid(); + let tasks = env.tasks().clone(); + handle.thread.set_status_running(); + + // If we need to rewind then do so + if let Some((mut rewind_state, trigger_res)) = rewind_state { + if rewind_state.is_64bit { + if let Err(exit_code) = + rewind_state.rewinding_finish::(&ctx, &mut store, trigger_res) + { + ctx.data(&store).blocking_cleanup(Some(exit_code)); + return; + } + let res = rewind::( + ctx.env.clone().into_mut(&mut store), + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + ); + if res != Errno::Success { + ctx.data(&store).blocking_cleanup(Some(res.into())); + return; + } + } else { + if let Err(exit_code) = + rewind_state.rewinding_finish::(&ctx, &mut store, trigger_res) + { + ctx.data(&store).blocking_cleanup(Some(exit_code)); + return; + } + let res = rewind::( + ctx.env.clone().into_mut(&mut store), + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + ); + if res != Errno::Success { + ctx.data(&store).blocking_cleanup(Some(res.into())); + return; + } + }; + } + + // Invoke the start function + let ret = { + // Call the module + let call_ret = if let Some(start) = get_start(&ctx, &store) { + start.call(&mut store, &[]) + } else { + debug!("wasi[{}]::exec-failed: missing _start function", pid); + ctx.data(&store) + .blocking_cleanup(Some(Errno::Noexec.into())); + return; + }; + + if let Err(err) = call_ret { + match err.downcast::() { + Ok(WasiError::Exit(code)) => { + if code.is_success() { + Ok(Errno::Success) + } else { + Ok(Errno::Noexec) + } + } + Ok(WasiError::DeepSleep(deep)) => { + // Create the callback that will be invoked when the thread respawns after a deep sleep + let rewind = deep.rewind; + let respawn = { + move |ctx, store, trigger_res| { + // Call the thread + call_module(ctx, store, handle, Some((rewind, trigger_res))); + } + }; + + // Spawns the WASM process after a trigger + if let Err(err) = + tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger) + { + debug!("failed to go into deep sleep - {}", err); + } + return; + } + Ok(WasiError::UnknownWasiVersion) => { + debug!("failed as wasi version is unknown",); + Ok(Errno::Noexec) + } + Err(err) => Err(WasiRuntimeError::from(err)), + } + } else { + Ok(Errno::Success) + } + }; + + let code = if let Err(err) = &ret { + err.as_exit_code().unwrap_or_else(|| Errno::Noexec.into()) + } else { + Errno::Success.into() + }; + + // Cleanup the environment + ctx.data(&store).blocking_cleanup(Some(code)); + + debug!("wasi[{pid}]::main() has exited with {code}"); + handle.thread.set_status_finished(ret.map(|a| a.into())); +} + impl BinFactory { pub fn spawn<'a>( &'a self, diff --git a/lib/wasi/src/capabilities.rs b/lib/wasi/src/capabilities.rs index bb4b9127c16..1468184e080 100644 --- a/lib/wasi/src/capabilities.rs +++ b/lib/wasi/src/capabilities.rs @@ -31,4 +31,8 @@ pub struct CapabilityThreadingV1 { /// /// [`None`] means no limit. pub max_threads: Option, + + /// Flag that indicates if asynchronous threading is disabled + /// (default = false) + pub enable_asynchronous_threading: bool, } diff --git a/lib/wasi/src/fs/inode_guard.rs b/lib/wasi/src/fs/inode_guard.rs index 100843ac793..56a30d19d86 100644 --- a/lib/wasi/src/fs/inode_guard.rs +++ b/lib/wasi/src/fs/inode_guard.rs @@ -72,41 +72,53 @@ impl InodeValFilePollGuard { impl std::fmt::Debug for InodeValFilePollGuard { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self.mode { - InodeValFilePollGuardMode::File(..) => write!(f, "guard-file"), + InodeValFilePollGuardMode::File(..) => { + write!(f, "guard-file(fd={}, peb={})", self.fd, self.peb) + } InodeValFilePollGuardMode::EventNotifications { .. } => { - write!(f, "guard-notifications") + write!(f, "guard-notifications(fd={}, peb={})", self.fd, self.peb) } InodeValFilePollGuardMode::Socket { inner } => { let inner = inner.protected.read().unwrap(); match inner.kind { - InodeSocketKind::TcpListener { .. } => write!(f, "guard-tcp-listener"), + InodeSocketKind::TcpListener { .. } => { + write!(f, "guard-tcp-listener(fd={}, peb={})", self.fd, self.peb) + } InodeSocketKind::TcpStream { ref socket, .. } => { if socket.is_closed() { - write!(f, "guard-tcp-stream (closed)") + write!( + f, + "guard-tcp-stream (closed, fd={}, peb={})", + self.fd, self.peb + ) } else { - write!(f, "guard-tcp-stream") + write!(f, "guard-tcp-stream(fd={}, peb={})", self.fd, self.peb) } } - InodeSocketKind::UdpSocket { .. } => write!(f, "guard-udp-socket"), - InodeSocketKind::Raw(..) => write!(f, "guard-raw-socket"), - _ => write!(f, "guard-socket"), + InodeSocketKind::UdpSocket { .. } => { + write!(f, "guard-udp-socket(fd={}, peb={})", self.fd, self.peb) + } + InodeSocketKind::Raw(..) => { + write!(f, "guard-raw-socket(fd={}, peb={})", self.fd, self.peb) + } + _ => write!(f, "guard-socket(fd={}), peb={})", self.fd, self.peb), } } } } } -pub(crate) struct InodeValFilePollGuardJoin<'a> { - mode: &'a mut InodeValFilePollGuardMode, +pub(crate) struct InodeValFilePollGuardJoin { + mode: InodeValFilePollGuardMode, fd: u32, peb: PollEventSet, subscription: Subscription, } -impl<'a> InodeValFilePollGuardJoin<'a> { - pub(crate) fn new(guard: &'a mut InodeValFilePollGuard) -> Self { +impl InodeValFilePollGuardJoin { + pub(crate) fn new(guard: InodeValFilePollGuard) -> Self { Self { - mode: &mut guard.mode, + mode: guard.mode, fd: guard.fd, peb: guard.peb, subscription: guard.subscription, @@ -115,9 +127,12 @@ impl<'a> InodeValFilePollGuardJoin<'a> { pub(crate) fn fd(&self) -> u32 { self.fd } + pub(crate) fn peb(&self) -> PollEventSet { + self.peb + } } -impl<'a> Future for InodeValFilePollGuardJoin<'a> { +impl Future for InodeValFilePollGuardJoin { type Output = heapless::Vec; fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { @@ -232,7 +247,14 @@ impl<'a> Future for InodeValFilePollGuardJoin<'a> { Poll::Pending } } - res => res, + Poll::Ready(Ok(amt)) => { + if guard.notifications.closed { + Poll::Pending + } else { + Poll::Ready(Ok(amt)) + } + } + Poll::Pending => Poll::Pending, } } }; @@ -303,7 +325,7 @@ impl<'a> Future for InodeValFilePollGuardJoin<'a> { let res = guard.poll_write_ready(cx).map_err(net_error_into_io_err); match res { Poll::Ready(Err(err)) if is_err_closed(&err) => { - tracing::trace!("socket write ready error (fd={}) - {}", fd, err); + tracing::trace!("socket write ready error (fd={}) - err={}", fd, err); if !replace(&mut guard.notifications.closed, true) { Poll::Ready(Ok(0)) } else { @@ -318,7 +340,14 @@ impl<'a> Future for InodeValFilePollGuardJoin<'a> { Poll::Pending } } - res => res, + Poll::Ready(Ok(amt)) => { + if guard.notifications.closed { + Poll::Pending + } else { + Poll::Ready(Ok(amt)) + } + } + Poll::Pending => Poll::Pending, } } }; diff --git a/lib/wasi/src/fs/mod.rs b/lib/wasi/src/fs/mod.rs index db2637e2947..f2f5c2c381b 100644 --- a/lib/wasi/src/fs/mod.rs +++ b/lib/wasi/src/fs/mod.rs @@ -1748,7 +1748,11 @@ impl WasiFs { Ok(fd_ref) => { let inode = fd_ref.inode.ino().as_u64(); let ref_cnt = fd_ref.inode.ref_cnt(); - trace!(%fd, %inode, %ref_cnt, "closing file descriptor"); + if ref_cnt == 1 { + trace!(%fd, %inode, %ref_cnt, "closing file descriptor"); + } else { + trace!(%fd, %inode, %ref_cnt, "weakening file descriptor"); + } } Err(err) => { trace!(%fd, "closing file descriptor failed - {}", err); diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index 90f194c844f..177688ff491 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -38,6 +38,7 @@ pub mod net; // TODO: should this be pub? pub mod fs; pub mod http; +mod rewind; #[cfg(feature = "webc_runner")] pub mod runners; pub mod runtime; @@ -47,15 +48,13 @@ mod utils; pub mod wapm; pub mod capabilities; +pub use rewind::*; /// WAI based bindings. mod bindings; use std::sync::Arc; -use std::{ - cell::RefCell, - sync::atomic::{AtomicU32, Ordering}, -}; +use std::{cell::RefCell, sync::atomic::AtomicU32}; #[allow(unused_imports)] use bytes::{Bytes, BytesMut}; @@ -98,7 +97,7 @@ pub use crate::{ }, runtime::{ task_manager::{VirtualTaskManager, VirtualTaskManagerExt}, - PluggableRuntime, SpawnedMemory, WasiRuntime, + PluggableRuntime, WasiRuntime, }, wapm::parse_static_webc, }; @@ -110,50 +109,26 @@ pub use crate::{ WasiEnv, WasiEnvBuilder, WasiEnvInit, WasiFunctionEnv, WasiInstanceHandles, WasiStateCreationError, ALL_RIGHTS, }, - syscalls::types, - utils::{get_wasi_version, get_wasi_versions, is_wasi_module, WasiVersion}, + syscalls::{rewind, types, unwind}, + utils::{ + get_wasi_version, get_wasi_versions, is_wasi_module, + store::{capture_snapshot, restore_snapshot, InstanceSnapshot}, + WasiVersion, + }, }; /// This is returned in `RuntimeError`. /// Use `downcast` or `downcast_ref` to retrieve the `ExitCode`. -#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Error, Debug)] pub enum WasiError { #[error("WASI exited with code: {0}")] Exit(ExitCode), + #[error("WASI deep sleep: {0:?}")] + DeepSleep(DeepSleepWork), #[error("The WASI version could not be determined")] UnknownWasiVersion, } -/// Represents the ID of a WASI calling thread -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WasiCallingId(u32); - -impl WasiCallingId { - pub fn raw(&self) -> u32 { - self.0 - } - - pub fn inc(&mut self) -> WasiCallingId { - self.0 += 1; - *self - } -} - -impl From for WasiCallingId { - fn from(id: u32) -> Self { - Self(id) - } -} -impl From for u32 { - fn from(t: WasiCallingId) -> u32 { - t.0 as u32 - } -} - -/// The default stack size for WASIX -pub const DEFAULT_STACK_SIZE: u64 = 1_048_576u64; -pub const DEFAULT_STACK_BASE: u64 = DEFAULT_STACK_SIZE; - // TODO: remove, this is a leftover from an old vbus crate and should be folded // into WasiRuntimeError. #[derive(Error, Copy, Clone, Debug, PartialEq, Eq)] @@ -232,7 +207,7 @@ pub enum WasiRuntimeError { Wasi(#[from] WasiError), #[error("Process manager error")] ControlPlane(#[from] ControlPlaneError), - #[error("Runtime error")] + #[error("{0}")] Runtime(#[from] RuntimeError), #[error("Memory access error")] Thread(#[from] WasiThreadError), @@ -308,22 +283,14 @@ pub struct WasiVFork { pub handle: WasiThreadHandle, } -impl WasiVFork { - /// Clones this env. - /// - /// This is a custom function instead of a [`Clone`] implementation because - /// this type should not be cloned. - /// - // TODO: remove WasiEnv::duplicate() - // This function should not exist, since it just copies internal state. - // Currently only used by fork/spawn related syscalls. - pub(crate) fn duplicate(&self) -> Self { +impl Clone for WasiVFork { + fn clone(&self) -> Self { Self { rewind_stack: self.rewind_stack.clone(), memory_stack: self.memory_stack.clone(), store_data: self.store_data.clone(), pid_offset: self.pid_offset, - env: Box::new(self.env.duplicate()), + env: Box::new(self.env.as_ref().clone()), handle: self.handle.clone(), } } @@ -336,20 +303,9 @@ lazy_static::lazy_static! { static ref CALLER_ID_SEED: Arc = Arc::new(AtomicU32::new(1)); } -/// Returns the current thread ID -pub fn current_caller_id() -> WasiCallingId { - CALLER_ID - .with(|f| { - let mut caller_id = f.borrow_mut(); - if *caller_id == 0 { - *caller_id = CALLER_ID_SEED.fetch_add(1, Ordering::AcqRel); - } - *caller_id - }) - .into() -} - -/// Create an [`Imports`] with an existing [`WasiEnv`]. +/// Create an [`Imports`] with an existing [`WasiEnv`]. `WasiEnv` +/// needs a [`WasiState`], that can be constructed from a +/// [`WasiEnvBuilder`](state::WasiEnvBuilder). pub fn generate_import_object_from_env( store: &mut impl AsStoreMut, ctx: &FunctionEnv, @@ -413,11 +369,11 @@ fn wasi_unstable_exports(mut store: &mut impl AsStoreMut, env: &FunctionEnv Function::new_typed_with_env(&mut store, env, path_rename::), "path_symlink" => Function::new_typed_with_env(&mut store, env, path_symlink::), "path_unlink_file" => Function::new_typed_with_env(&mut store, env, path_unlink_file::), - "poll_oneoff" => Function::new_typed_with_env(&mut store, env, legacy::snapshot0::poll_oneoff), + "poll_oneoff" => Function::new_typed_with_env(&mut store, env, legacy::snapshot0::poll_oneoff::), "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), + "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield::), "sock_recv" => Function::new_typed_with_env(&mut store, env, sock_recv::), "sock_send" => Function::new_typed_with_env(&mut store, env, sock_send::), "sock_shutdown" => Function::new_typed_with_env(&mut store, env, sock_shutdown), @@ -473,7 +429,7 @@ fn wasi_snapshot_preview1_exports( "proc_exit" => Function::new_typed_with_env(&mut store, env, proc_exit::), "proc_raise" => Function::new_typed_with_env(&mut store, env, proc_raise), "random_get" => Function::new_typed_with_env(&mut store, env, random_get::), - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), + "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield::), "sock_recv" => Function::new_typed_with_env(&mut store, env, sock_recv::), "sock_send" => Function::new_typed_with_env(&mut store, env, sock_send::), "sock_shutdown" => Function::new_typed_with_env(&mut store, env, sock_shutdown), @@ -551,13 +507,13 @@ fn wasix_exports_32(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "thread_local_destroy" => Function::new_typed_with_env(&mut store, env, thread_local_destroy), "thread_local_set" => Function::new_typed_with_env(&mut store, env, thread_local_set), "thread_local_get" => Function::new_typed_with_env(&mut store, env, thread_local_get::), - "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep), + "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep::), "thread_id" => Function::new_typed_with_env(&mut store, env, thread_id::), "thread_signal" => Function::new_typed_with_env(&mut store, env, thread_signal), - "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join), + "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join::), "thread_parallelism" => Function::new_typed_with_env(&mut store, env, thread_parallelism::), "thread_exit" => Function::new_typed_with_env(&mut store, env, thread_exit), - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), + "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield::), "stack_checkpoint" => Function::new_typed_with_env(&mut store, env, stack_checkpoint::), "stack_restore" => Function::new_typed_with_env(&mut store, env, stack_restore::), "futex_wait" => Function::new_typed_with_env(&mut store, env, futex_wait::), @@ -674,13 +630,13 @@ fn wasix_exports_64(mut store: &mut impl AsStoreMut, env: &FunctionEnv) "thread_local_destroy" => Function::new_typed_with_env(&mut store, env, thread_local_destroy), "thread_local_set" => Function::new_typed_with_env(&mut store, env, thread_local_set), "thread_local_get" => Function::new_typed_with_env(&mut store, env, thread_local_get::), - "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep), + "thread_sleep" => Function::new_typed_with_env(&mut store, env, thread_sleep::), "thread_id" => Function::new_typed_with_env(&mut store, env, thread_id::), "thread_signal" => Function::new_typed_with_env(&mut store, env, thread_signal), - "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join), + "thread_join" => Function::new_typed_with_env(&mut store, env, thread_join::), "thread_parallelism" => Function::new_typed_with_env(&mut store, env, thread_parallelism::), "thread_exit" => Function::new_typed_with_env(&mut store, env, thread_exit), - "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield), + "sched_yield" => Function::new_typed_with_env(&mut store, env, sched_yield::), "stack_checkpoint" => Function::new_typed_with_env(&mut store, env, stack_checkpoint::), "stack_restore" => Function::new_typed_with_env(&mut store, env, stack_restore::), "futex_wait" => Function::new_typed_with_env(&mut store, env, futex_wait::), diff --git a/lib/wasi/src/os/task/control_plane.rs b/lib/wasi/src/os/task/control_plane.rs index 19fa8e87fbf..be064ac4c80 100644 --- a/lib/wasi/src/os/task/control_plane.rs +++ b/lib/wasi/src/os/task/control_plane.rs @@ -39,12 +39,15 @@ impl WasiControlPlaneHandle { pub struct ControlPlaneConfig { /// Total number of tasks (processes + threads) that can be spawned. pub max_task_count: Option, + /// Flag that indicates if asynchronous threading is enables (opt-in) + pub enable_asynchronous_threading: bool, } impl ControlPlaneConfig { pub fn new() -> Self { Self { max_task_count: None, + enable_asynchronous_threading: false, } } } @@ -98,6 +101,11 @@ impl WasiControlPlane { self.state.task_count.load(Ordering::SeqCst) } + /// Returns the configuration for this control plane + pub(crate) fn config(&self) -> &ControlPlaneConfig { + &self.state.config + } + /// Register a new task. /// // Currently just increments the task counter. @@ -202,6 +210,7 @@ mod tests { fn test_control_plane_task_limits() { let p = WasiControlPlane::new(ControlPlaneConfig { max_task_count: Some(2), + enable_asynchronous_threading: false, }); let p1 = p.new_process().unwrap(); @@ -219,6 +228,7 @@ mod tests { fn test_control_plane_task_limits_with_dropped_threads() { let p = WasiControlPlane::new(ControlPlaneConfig { max_task_count: Some(2), + enable_asynchronous_threading: false, }); let p1 = p.new_process().unwrap(); diff --git a/lib/wasi/src/os/task/process.rs b/lib/wasi/src/os/task/process.rs index 69972cffbaf..5eb3a9e9869 100644 --- a/lib/wasi/src/os/task/process.rs +++ b/lib/wasi/src/os/task/process.rs @@ -236,6 +236,9 @@ impl WasiProcess { } let tid: WasiThreadId = tid.into(); + let pid = self.pid(); + tracing::trace!(%pid, %tid, "signal-thread({:?})", signal); + let inner = self.inner.read().unwrap(); if let Some(thread) = inner.threads.get(&tid) { thread.signal(signal); @@ -251,6 +254,9 @@ impl WasiProcess { /// Signals all the threads in this process pub fn signal_process(&self, signal: Signal) { + let pid = self.pid(); + tracing::trace!(%pid, "signal-process({:?})", signal); + { let inner = self.inner.read().unwrap(); if self.waiting.load(Ordering::Acquire) > 0 { diff --git a/lib/wasi/src/os/task/thread.rs b/lib/wasi/src/os/task/thread.rs index 99a106650c6..15fef9f4bfd 100644 --- a/lib/wasi/src/os/task/thread.rs +++ b/lib/wasi/src/os/task/thread.rs @@ -6,6 +6,7 @@ use std::{ }; use bytes::{Bytes, BytesMut}; +use wasmer::{ExportError, InstantiationError, MemoryError}; use wasmer_wasix_types::{ types::Signal, wasi::{Errno, ExitCode}, @@ -121,6 +122,36 @@ impl Drop for WasiThreadRunGuard { } } +/// Represents the memory layout of the parts that the thread itself uses +#[derive(Debug, Clone)] +pub struct WasiMemoryLayout { + /// This is the top part of the stack (stacks go backwards) + pub stack_upper: u64, + /// This is the bottom part of the stack (anything more below this is a stack overflow) + pub stack_lower: u64, + /// Piece of memory that is marked as none readable/writable so stack overflows cause an exception + /// TODO: This field will need to be used to mark the guard memory as inaccessible + #[allow(dead_code)] + pub guard_size: u64, + /// Total size of the stack + pub stack_size: u64, +} + +/// The default stack size for WASIX +pub const DEFAULT_STACK_SIZE: u64 = 1_048_576u64; +pub const DEFAULT_STACK_BASE: u64 = DEFAULT_STACK_SIZE; + +impl Default for WasiMemoryLayout { + fn default() -> Self { + Self { + stack_lower: 0, + stack_upper: DEFAULT_STACK_SIZE, + guard_size: 0, + stack_size: DEFAULT_STACK_SIZE, + } + } +} + #[derive(Debug)] struct WasiThreadState { is_main: bool, @@ -187,6 +218,21 @@ impl WasiThread { self.state.status.set_running(); } + /// Gets or sets the exit code based of a signal that was received + /// Note: if the exit code was already set earlier this method will + /// just return that earlier set exit code + pub fn set_or_get_exit_code_for_signal(&self, sig: Signal) -> ExitCode { + let default_exitcode: ExitCode = match sig { + Signal::Sigquit | Signal::Sigabrt => Errno::Success.into(), + _ => Errno::Intr.into(), + }; + // This will only set the status code if its not already set + self.set_status_finished(Ok(default_exitcode)); + self.try_join() + .map(|r| r.unwrap_or(default_exitcode)) + .unwrap_or(default_exitcode) + } + /// Marks the thread as finished (which will cause anyone that /// joined on it to wake up) pub fn set_status_finished(&self, res: Result) { @@ -445,8 +491,14 @@ pub enum WasiThreadError { Unsupported, #[error("The method named is not an exported function")] MethodNotFound, - #[error("Failed to create the requested memory")] - MemoryCreateFailed, + #[error("Failed to create the requested memory - {0}")] + MemoryCreateFailed(MemoryError), + #[error("{0}")] + ExportError(ExportError), + #[error("Failed to create the instance")] + InstanceCreateFailed(InstantiationError), + #[error("Initialization function failed - {0}")] + InitFailed(anyhow::Error), /// This will happen if WASM is running in a thread has not been created by the spawn_wasm call #[error("WASM context is invalid")] InvalidWasmContext, @@ -457,7 +509,10 @@ impl From for Errno { match a { WasiThreadError::Unsupported => Errno::Notsup, WasiThreadError::MethodNotFound => Errno::Inval, - WasiThreadError::MemoryCreateFailed => Errno::Nomem, + WasiThreadError::MemoryCreateFailed(_) => Errno::Nomem, + WasiThreadError::ExportError(_) => Errno::Noexec, + WasiThreadError::InstanceCreateFailed(_) => Errno::Noexec, + WasiThreadError::InitFailed(_) => Errno::Noexec, WasiThreadError::InvalidWasmContext => Errno::Noexec, } } diff --git a/lib/wasi/src/rewind.rs b/lib/wasi/src/rewind.rs new file mode 100644 index 00000000000..b2bca34f1b6 --- /dev/null +++ b/lib/wasi/src/rewind.rs @@ -0,0 +1,91 @@ +use std::pin::Pin; + +use bytes::Bytes; +use futures::Future; +use wasmer::{AsStoreMut, AsStoreRef, MemorySize}; +use wasmer_wasix_types::wasi::{Errno, ExitCode}; + +use crate::{ + syscalls::{get_memory_stack, set_memory_stack}, + WasiEnv, WasiFunctionEnv, +}; + +/// Future that will be polled by asyncify methods +#[doc(hidden)] +pub type AsyncifyFuture = dyn Future> + Send + Sync + 'static; + +/// Trait that will be invoked after the rewind has finished +/// It is possible that the process will be terminated rather +/// than restored at this point +#[doc(hidden)] +pub trait RewindPostProcess { + fn finish( + &mut self, + env: &WasiEnv, + store: &dyn AsStoreRef, + res: Result<(), Errno>, + ) -> Result<(), ExitCode>; +} + +/// The rewind state after a deep sleep +#[doc(hidden)] +pub struct RewindState { + /// Memory stack used to restore the stack trace back to where it was + pub memory_stack: Bytes, + /// Call stack used to restore the stack trace back to where it was + pub rewind_stack: Bytes, + /// All the global data stored in the store + pub store_data: Bytes, + /// Flag that indicates if this rewind is 64-bit or 32-bit memory based + pub is_64bit: bool, + /// This is the function that's invoked after the work is finished + /// and the rewind has been applied. + pub finish: Box, +} + +impl RewindState { + #[doc(hidden)] + pub fn rewinding_finish( + &mut self, + ctx: &WasiFunctionEnv, + store: &mut impl AsStoreMut, + res: Result<(), Errno>, + ) -> Result<(), ExitCode> { + let mut ctx = ctx.env.clone().into_mut(store); + let (env, mut store) = ctx.data_and_store_mut(); + set_memory_stack::(env, &mut store, self.memory_stack.clone()).map_err(|err| { + tracing::error!("failed on rewinding_finish - {}", err); + ExitCode::Errno(Errno::Memviolation) + })?; + let ret = self.finish.finish(env, &store, res); + if ret.is_ok() { + self.memory_stack = get_memory_stack::(env, &mut store) + .map_err(|err| { + tracing::error!("failed on rewinding_finish - {}", err); + ExitCode::Errno(Errno::Memviolation) + })? + .freeze(); + } + ret + } +} + +/// Represents the work that will be done when a thread goes to deep sleep and +/// includes the things needed to restore it again +pub struct DeepSleepWork { + /// This is the work that will be performed before the thread is rewoken + pub trigger: Pin>, + /// State that the thread will be rewound to + pub rewind: RewindState, +} +impl std::fmt::Debug for DeepSleepWork { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "deep-sleep-work(memory_stack_len={}, rewind_stack_len={}, store_size={})", + self.rewind.memory_stack.len(), + self.rewind.rewind_stack.len(), + self.rewind.store_data.len() + ) + } +} diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index 527258f885c..f1ddb77cca9 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -2,7 +2,9 @@ pub mod module_cache; pub mod resolver; pub mod task_manager; -pub use self::task_manager::{SpawnType, SpawnedMemory, VirtualTaskManager}; +use crate::{http::DynHttpClient, os::TtyBridge, WasiTtyState}; + +pub use self::task_manager::{SpawnMemoryType, VirtualTaskManager}; use std::{ fmt, @@ -12,14 +14,9 @@ use std::{ use derivative::Derivative; use virtual_net::{DynVirtualNetworking, VirtualNetworking}; -use crate::{ - http::DynHttpClient, - os::TtyBridge, - runtime::{ - module_cache::ModuleCache, - resolver::{PackageResolver, RegistryResolver}, - }, - WasiTtyState, +use crate::runtime::{ + module_cache::ModuleCache, + resolver::{PackageResolver, RegistryResolver}, }; /// Represents an implementation of the WASI runtime - by default everything is diff --git a/lib/wasi/src/runtime/task_manager/mod.rs b/lib/wasi/src/runtime/task_manager/mod.rs index 1e3f7bbd34d..9adadbe3b1d 100644 --- a/lib/wasi/src/runtime/task_manager/mod.rs +++ b/lib/wasi/src/runtime/task_manager/mod.rs @@ -2,28 +2,84 @@ #[cfg(feature = "sys-thread")] pub mod tokio; -use std::{ops::Deref, pin::Pin, time::Duration}; +use std::ops::Deref; +use std::task::{Context, Poll}; +use std::{pin::Pin, time::Duration}; use ::tokio::runtime::Handle; use futures::Future; -use wasmer::{Memory, MemoryType, Module, Store, StoreMut}; +use wasmer::{AsStoreMut, AsStoreRef, Memory, MemoryType, Module, Store, StoreMut, StoreRef}; +use wasmer_wasix_types::wasi::{Errno, ExitCode}; use crate::os::task::thread::WasiThreadError; +use crate::syscalls::AsyncifyFuture; +use crate::{capture_snapshot, InstanceSnapshot, WasiEnv, WasiFunctionEnv, WasiThread}; #[derive(Debug)] -pub struct SpawnedMemory { - pub ty: MemoryType, +pub enum SpawnMemoryType<'a> { + CreateMemory, + CreateMemoryOfType(MemoryType), + // TODO: is there a way to get rid of the memory reference + ShareMemory(Memory, StoreRef<'a>), + // TODO: is there a way to get rid of the memory reference + CopyMemory(Memory, StoreRef<'a>), } -#[derive(Debug)] -pub enum SpawnType { - Create, - CreateWithType(SpawnedMemory), - NewThread(Memory), +pub type WasmResumeTask = dyn FnOnce(WasiFunctionEnv, Store, Result<(), Errno>) + Send + 'static; + +pub type WasmResumeTrigger = + dyn FnOnce() -> Pin> + Send + 'static>> + Send + Sync; + +/// The properties passed to the task +pub struct TaskWasmRunProperties { + pub ctx: WasiFunctionEnv, + pub store: Store, + pub result: Result<(), Errno>, +} + +/// Callback that will be invoked +pub type TaskWasmRun = dyn FnOnce(TaskWasmRunProperties) + Send + 'static; + +/// Represents a WASM task that will be executed on a dedicated thread +pub struct TaskWasm<'a, 'b> { + pub run: Box, + pub env: WasiEnv, + pub module: Module, + pub snapshot: Option<&'b InstanceSnapshot>, + pub spawn_type: SpawnMemoryType<'a>, + pub trigger: Option>, + pub update_layout: bool, +} +impl<'a, 'b> TaskWasm<'a, 'b> { + pub fn new(run: Box, env: WasiEnv, module: Module, update_layout: bool) -> Self { + Self { + run, + env, + module, + snapshot: None, + spawn_type: SpawnMemoryType::CreateMemory, + trigger: None, + update_layout, + } + } + + pub fn with_memory(mut self, spawn_type: SpawnMemoryType<'a>) -> Self { + self.spawn_type = spawn_type; + self + } + + pub fn with_snapshot(mut self, snapshot: &'b InstanceSnapshot) -> Self { + self.snapshot.replace(snapshot); + self + } + + pub fn with_trigger(mut self, trigger: Box) -> Self { + self.trigger.replace(trigger); + self + } } /// An implementation of task management -#[async_trait::async_trait] #[allow(unused_variables)] pub trait VirtualTaskManager: std::fmt::Debug + Send + Sync + 'static { /// Build a new Webassembly memory. @@ -31,14 +87,43 @@ pub trait VirtualTaskManager: std::fmt::Debug + Send + Sync + 'static { /// May return `None` if the memory can just be auto-constructed. fn build_memory( &self, - store: &mut StoreMut, - spawn_type: SpawnType, - ) -> Result, WasiThreadError>; + mut store: &mut StoreMut, + spawn_type: SpawnMemoryType, + ) -> Result, WasiThreadError> { + match spawn_type { + SpawnMemoryType::CreateMemoryOfType(mut ty) => { + ty.shared = true; + let mem = Memory::new(&mut store, ty).map_err(|err| { + tracing::error!("could not create memory: {err}"); + WasiThreadError::MemoryCreateFailed(err) + })?; + Ok(Some(mem)) + } + SpawnMemoryType::ShareMemory(mem, old_store) => { + let mem = mem.share_in_store(&old_store, store).map_err(|err| { + tracing::warn!("could not clone memory: {err}"); + WasiThreadError::MemoryCreateFailed(err) + })?; + Ok(Some(mem)) + } + SpawnMemoryType::CopyMemory(mem, old_store) => { + let mem = mem.copy_to_store(&old_store, store).map_err(|err| { + tracing::warn!("could not copy memory: {err}"); + WasiThreadError::MemoryCreateFailed(err) + })?; + Ok(Some(mem)) + } + SpawnMemoryType::CreateMemory => Ok(None), + } + } /// Invokes whenever a WASM thread goes idle. In some runtimes (like singlethreaded /// execution environments) they will need to do asynchronous work whenever the main /// thread goes idle and this is the place to hook for that. - async fn sleep_now(&self, time: Duration); + fn sleep_now( + &self, + time: Duration, + ) -> Pin + Send + Sync + 'static>>; /// Starts an asynchronous task that will run on a shared worker pool /// This task must not block the execution or it could cause a deadlock @@ -56,16 +141,9 @@ pub trait VirtualTaskManager: std::fmt::Debug + Send + Sync + 'static { #[allow(dyn_drop)] fn runtime_enter<'g>(&'g self) -> Box; - /// Starts an asynchronous task will will run on a dedicated thread + /// Starts an WebAssembly task will will run on a dedicated thread /// pulled from the worker pool that has a stateful thread local variable - /// It is ok for this task to block execution and any async futures within its scope - fn task_wasm( - &self, - task: Box) + Send + 'static>, - store: Store, - module: Module, - spawn_type: SpawnType, - ) -> Result<(), WasiThreadError>; + fn task_wasm(&self, task: TaskWasm) -> Result<(), WasiThreadError>; /// Starts an asynchronous task will will run on a dedicated thread /// pulled from the worker pool. It is ok for this task to block execution @@ -79,7 +157,6 @@ pub trait VirtualTaskManager: std::fmt::Debug + Send + Sync + 'static { fn thread_parallelism(&self) -> Result; } -#[async_trait::async_trait] impl VirtualTaskManager for D where D: Deref + std::fmt::Debug + Send + Sync + 'static, @@ -88,13 +165,16 @@ where fn build_memory( &self, store: &mut StoreMut, - spawn_type: SpawnType, + spawn_type: SpawnMemoryType, ) -> Result, WasiThreadError> { (**self).build_memory(store, spawn_type) } - async fn sleep_now(&self, time: Duration) { - (**self).sleep_now(time).await + fn sleep_now( + &self, + time: Duration, + ) -> Pin + Send + Sync + 'static>> { + (**self).sleep_now(time) } fn task_shared( @@ -115,14 +195,8 @@ where (**self).runtime_enter() } - fn task_wasm( - &self, - task: Box) + Send + 'static>, - store: Store, - module: Module, - spawn_type: SpawnType, - ) -> Result<(), WasiThreadError> { - (**self).task_wasm(task, store, module, spawn_type) + fn task_wasm(&self, task: TaskWasm) -> Result<(), WasiThreadError> { + (**self).task_wasm(task) } fn task_dedicated( @@ -144,6 +218,76 @@ impl dyn VirtualTaskManager { pub fn block_on<'a, A>(&self, task: impl Future + 'a) -> A { self.runtime().block_on(task) } + + /// Starts an WebAssembly task will will run on a dedicated thread + /// pulled from the worker pool that has a stateful thread local variable + /// After the poller has successed + #[doc(hidden)] + pub fn resume_wasm_after_poller( + &self, + task: Box, + ctx: WasiFunctionEnv, + mut store: Store, + trigger: Pin>, + ) -> Result<(), WasiThreadError> { + // This poller will process any signals when the main working function is idle + struct AsyncifyPollerOwned { + thread: WasiThread, + trigger: Pin>, + } + impl Future for AsyncifyPollerOwned { + type Output = Result, ExitCode>; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let work = self.trigger.as_mut(); + Poll::Ready(if let Poll::Ready(res) = work.poll(cx) { + Ok(res) + } else if let Some(forced_exit) = self.thread.try_join() { + return Poll::Ready(Err(forced_exit.unwrap_or_else(|err| { + tracing::debug!("exit runtime error - {}", err); + Errno::Child.into() + }))); + } else if self.thread.has_signals_or_subscribe(cx.waker()) { + Ok(Err(Errno::Intr)) + } else { + return Poll::Pending; + }) + } + } + + let snapshot = capture_snapshot(&mut store.as_store_mut()); + let env = ctx.data(&store); + let module = env.inner().module_clone(); + let memory = env.inner().memory_clone(); + let thread = env.thread.clone(); + let env = env.clone(); + + self.task_wasm( + TaskWasm::new( + Box::new(move |props| task(props.ctx, props.store, props.result)), + env.clone(), + module, + false, + ) + .with_memory(SpawnMemoryType::ShareMemory(memory, store.as_store_ref())) + .with_snapshot(&snapshot) + .with_trigger(Box::new(move || { + Box::pin(async move { + let mut poller = AsyncifyPollerOwned { thread, trigger }; + let res = Pin::new(&mut poller).await; + let res = match res { + Ok(res) => res, + Err(exit_code) => { + env.thread.set_status_finished(Ok(exit_code)); + return Err(exit_code.into()); + } + }; + + tracing::trace!("deep sleep woken - {:?}", res); + res + }) + })), + ) + } } /// Generic utility methods for VirtualTaskManager diff --git a/lib/wasi/src/runtime/task_manager/tokio.rs b/lib/wasi/src/runtime/task_manager/tokio.rs index dade0380f29..b84b93bfe81 100644 --- a/lib/wasi/src/runtime/task_manager/tokio.rs +++ b/lib/wasi/src/runtime/task_manager/tokio.rs @@ -6,11 +6,10 @@ use std::{ use futures::Future; use tokio::runtime::Handle; -use wasmer::{AsStoreMut, Memory, Module, Store, StoreMut}; -use crate::os::task::thread::WasiThreadError; +use crate::{os::task::thread::WasiThreadError, WasiFunctionEnv}; -use super::{SpawnType, VirtualTaskManager}; +use super::{TaskWasm, TaskWasmRunProperties, VirtualTaskManager}; /// A task manager that uses tokio to spawn tasks. #[derive(Clone, Debug)] @@ -76,32 +75,16 @@ impl<'g> Drop for TokioRuntimeGuard<'g> { fn drop(&mut self) {} } -#[async_trait::async_trait] impl VirtualTaskManager for TokioTaskManager { - fn build_memory( - &self, - mut store: &mut StoreMut, - spawn_type: SpawnType, - ) -> Result, WasiThreadError> { - match spawn_type { - SpawnType::CreateWithType(mem) => Memory::new(&mut store, mem.ty) - .map_err(|err| { - tracing::error!("could not create memory: {err}"); - WasiThreadError::MemoryCreateFailed - }) - .map(Some), - SpawnType::NewThread(mem) => Ok(Some(mem)), - SpawnType::Create => Ok(None), - } - } - /// See [`VirtualTaskManager::sleep_now`]. - async fn sleep_now(&self, time: Duration) { - if time == Duration::ZERO { - tokio::task::yield_now().await; - } else { - tokio::time::sleep(time).await; - } + fn sleep_now(&self, time: Duration) -> Pin + Send + Sync>> { + Box::pin(async move { + if time == Duration::ZERO { + tokio::task::yield_now().await; + } else { + tokio::time::sleep(time).await; + } + }) } /// See [`VirtualTaskManager::task_shared`]. @@ -130,18 +113,46 @@ impl VirtualTaskManager for TokioTaskManager { }) } - fn task_wasm( - &self, - task: Box) + Send + 'static>, - mut store: Store, - module: Module, - spawn_type: SpawnType, - ) -> Result<(), WasiThreadError> { - let memory = self.build_memory(&mut store.as_store_mut(), spawn_type)?; - self.0.spawn_blocking(move || { - // Invoke the callback - task(store, module, memory); - }); + /// See [`VirtualTaskManager::task_wasm`]. + fn task_wasm(&self, task: TaskWasm) -> Result<(), WasiThreadError> { + // Create the context on a new store + let run = task.run; + let (ctx, store) = WasiFunctionEnv::new_with_store( + task.module, + task.env, + task.snapshot, + task.spawn_type, + task.update_layout, + )?; + + // If we have a trigger then we first need to run + // the poller to completion + if let Some(trigger) = task.trigger { + let trigger = trigger(); + let handle = self.0.clone(); + self.0.spawn(async move { + let res = trigger.await; + // Build the task that will go on the callback + handle.spawn_blocking(move || { + // Invoke the callback + run(TaskWasmRunProperties { + ctx, + store, + result: res, + }); + }); + }); + } else { + // Run the callback on a dedicated thread + self.0.spawn_blocking(move || { + // Invoke the callback + run(TaskWasmRunProperties { + ctx, + store, + result: Ok(()), + }); + }); + } Ok(()) } diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index 1e5ae200848..d716706f4c2 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -9,7 +9,7 @@ use std::{ use rand::Rng; use thiserror::Error; use virtual_fs::{ArcFile, FsError, TmpFileSystem, VirtualFile}; -use wasmer::{AsStoreMut, Instance, Module}; +use wasmer::{AsStoreMut, Instance, Module, Store}; use wasmer_wasix_types::wasi::Errno; #[cfg(feature = "sys")] @@ -687,7 +687,6 @@ impl WasiEnvBuilder { inodes, args: self.args.clone(), preopen: self.vfs_preopens.clone(), - threading: Default::default(), futexs: Default::default(), clock_offset: Default::default(), envs, @@ -714,6 +713,7 @@ impl WasiEnvBuilder { let plane_config = ControlPlaneConfig { max_task_count: capabilities.threading.max_threads, + enable_asynchronous_threading: capabilities.threading.enable_asynchronous_threading, }; let control_plane = WasiControlPlane::new(plane_config); @@ -725,10 +725,11 @@ impl WasiEnvBuilder { control_plane, bin_factory, capabilities, - spawn_type: None, + memory_ty: None, process: None, thread: None, call_initialize: true, + can_deep_sleep: false, }; Ok(init) @@ -778,11 +779,7 @@ impl WasiEnvBuilder { } #[allow(clippy::result_large_err)] - pub fn run_with_store( - self, - module: Module, - store: &mut impl AsStoreMut, - ) -> Result<(), WasiRuntimeError> { + pub fn run_with_store(self, module: Module, store: &mut Store) -> Result<(), WasiRuntimeError> { let (instance, env) = self.instantiate(module, store)?; let start = instance.exports.get_function("_start")?; diff --git a/lib/wasi/src/state/env.rs b/lib/wasi/src/state/env.rs index 883d8d19b20..301bf70d7e5 100644 --- a/lib/wasi/src/state/env.rs +++ b/lib/wasi/src/state/env.rs @@ -7,8 +7,8 @@ use tracing::{trace, warn}; use virtual_fs::{FsError, VirtualFile}; use virtual_net::DynVirtualNetworking; use wasmer::{ - AsStoreMut, AsStoreRef, FunctionEnvMut, Global, Instance, Memory, MemoryView, Module, - TypedFunction, + AsStoreMut, AsStoreRef, FunctionEnvMut, Global, Instance, Memory, MemoryType, MemoryView, + Module, TypedFunction, }; use wasmer_wasix_types::{ types::Signal, @@ -25,16 +25,16 @@ use crate::{ task::{ control_plane::ControlPlaneError, process::{WasiProcess, WasiProcessId}, - thread::{WasiThread, WasiThreadHandle, WasiThreadId}, + thread::{WasiMemoryLayout, WasiThread, WasiThreadHandle, WasiThreadId}, }, }, - runtime::SpawnType, + runtime::SpawnMemoryType, syscalls::{__asyncify_light, platform_clock_time_get}, - SpawnedMemory, VirtualTaskManager, WasiControlPlane, WasiEnvBuilder, WasiError, - WasiFunctionEnv, WasiRuntime, WasiRuntimeError, WasiStateCreationError, WasiVFork, - DEFAULT_STACK_SIZE, + VirtualTaskManager, WasiControlPlane, WasiEnvBuilder, WasiError, WasiFunctionEnv, WasiRuntime, + WasiRuntimeError, WasiStateCreationError, WasiVFork, }; +pub(crate) use super::handles::*; use super::WasiState; /// Various [`TypedFunction`] and [`Global`] handles for an active WASI(X) instance. @@ -55,8 +55,6 @@ pub struct WasiInstanceHandles { /// Main function that will be invoked (name = "_start") #[derivative(Debug = "ignore")] - // TODO: review allow... - #[allow(dead_code)] pub(crate) start: Option>, /// Function thats invoked to initialize the WASM module (name = "_initialize") @@ -189,14 +187,37 @@ impl WasiInstanceHandles { instance, } } -} -/// The code itself makes safe use of the struct so multiple threads don't access -/// it (without this the JS code prevents the reference to the module from being stored -/// which is needed for the multithreading mode) -unsafe impl Send for WasiInstanceHandles {} + pub fn module(&self) -> &Module { + self.instance.module() + } + + pub fn module_clone(&self) -> Module { + self.instance.module().clone() + } + + /// Providers safe access to the memory + /// (it must be initialized before it can be used) + pub fn memory_view<'a>(&'a self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> { + self.memory.view(store) + } + + /// Providers safe access to the memory + /// (it must be initialized before it can be used) + pub fn memory(&self) -> &Memory { + &self.memory + } + + /// Copy the lazy reference so that when it's initialized during the + /// export phase, all the other references get a copy of it + pub fn memory_clone(&self) -> Memory { + self.memory.clone() + } -unsafe impl Sync for WasiInstanceHandles {} + pub fn instance(&self) -> &Instance { + &self.instance + } +} /// Data required to construct a [`WasiEnv`]. #[derive(Debug)] @@ -209,15 +230,16 @@ pub struct WasiEnvInit { pub capabilities: Capabilities, pub control_plane: WasiControlPlane, - // TODO: remove these again? - // Only needed if WasiEnvInit is also used for process/thread spawning. - pub spawn_type: Option, + pub memory_ty: Option, pub process: Option, pub thread: Option, /// Whether to call the `_initialize` function in the WASI module. /// Will be true for regular new instances, but false for threads. pub call_initialize: bool, + + /// Indicates if the calling environment is capable of deep sleeping + pub can_deep_sleep: bool, } impl WasiEnvInit { @@ -234,7 +256,6 @@ impl WasiEnvInit { secret: rand::thread_rng().gen::<[u8; 32]>(), inodes, fs, - threading: Default::default(), futexs: Default::default(), clock_offset: std::sync::Mutex::new( self.state.clock_offset.lock().unwrap().clone(), @@ -249,10 +270,11 @@ impl WasiEnvInit { bin_factory: self.bin_factory.clone(), capabilities: self.capabilities.clone(), control_plane: self.control_plane.clone(), - spawn_type: None, + memory_ty: None, process: None, thread: None, call_initialize: self.call_initialize, + can_deep_sleep: self.can_deep_sleep, } } } @@ -264,12 +286,10 @@ pub struct WasiEnv { pub process: WasiProcess, /// Represents the thread this environment is attached to pub thread: WasiThread, + /// Represents the layout of the memory + pub layout: WasiMemoryLayout, /// Represents a fork of the process that is currently in play pub vfork: Option, - /// End of the stack memory that is allocated for this thread - pub stack_end: u64, - /// Start of the stack memory that is allocated for this thread - pub stack_start: u64, /// Seed used to rotate around the events returned by `poll_oneoff` pub poll_seed: u64, /// Shared state of the WASI system. Manages all the data that the @@ -277,8 +297,6 @@ pub struct WasiEnv { pub(crate) state: Arc, /// Binary factory attached to this environment pub bin_factory: BinFactory, - /// Inner functions and references that are loaded before the environment starts - pub inner: Option, /// List of the handles that are owned by this context /// (this can be used to ensure that threads own themselves or others) pub owned_handles: Vec, @@ -286,6 +304,15 @@ pub struct WasiEnv { pub runtime: Arc, pub capabilities: Capabilities, + + /// Is this environment capable and setup for deep sleeping + pub enable_deep_sleep: bool, + + /// Inner functions and references that are loaded before the environment starts + /// (inner is not safe to send between threads and so it is private and will + /// not be cloned when `WasiEnv` is cloned) + /// TODO: We should move this outside of `WasiEnv` with some refactoring + inner: WasiInstanceHandlesPointer, } impl std::fmt::Debug for WasiEnv { @@ -294,44 +321,31 @@ impl std::fmt::Debug for WasiEnv { } } -// FIXME: remove unsafe impls! -// Added because currently WasiEnv can hold a wasm_bindgen::JsValue via wasmer::Module. -#[cfg(feature = "js")] -unsafe impl Send for WasiEnv {} -#[cfg(feature = "js")] -unsafe impl Sync for WasiEnv {} - -impl WasiEnv { - /// Construct a new [`WasiEnvBuilder`] that allows customizing an environment. - pub fn builder(program_name: impl Into) -> WasiEnvBuilder { - WasiEnvBuilder::new(program_name) - } - - /// Clones this env. - /// - /// This is a custom function instead of a [`Clone`] implementation because - /// this type should not be cloned. - /// - // TODO: remove WasiEnv::duplicate() - // This function should not exist, since it just copies internal state. - // Currently only used by fork/spawn related syscalls. - pub(crate) fn duplicate(&self) -> Self { +impl Clone for WasiEnv { + fn clone(&self) -> Self { Self { control_plane: self.control_plane.clone(), process: self.process.clone(), poll_seed: self.poll_seed, thread: self.thread.clone(), - vfork: self.vfork.as_ref().map(|v| v.duplicate()), - stack_end: self.stack_end, - stack_start: self.stack_start, + layout: self.layout.clone(), + vfork: self.vfork.clone(), state: self.state.clone(), bin_factory: self.bin_factory.clone(), - inner: self.inner.clone(), + inner: Default::default(), owned_handles: self.owned_handles.clone(), runtime: self.runtime.clone(), capabilities: self.capabilities.clone(), + enable_deep_sleep: self.enable_deep_sleep, } } +} + +impl WasiEnv { + /// Construct a new [`WasiEnvBuilder`] that allows customizing an environment. + pub fn builder(program_name: impl Into) -> WasiEnvBuilder { + WasiEnvBuilder::new(program_name) + } /// Forking the WasiState is used when either fork or vfork is called pub fn fork(&self) -> Result<(Self, WasiThreadHandle), ControlPlaneError> { @@ -349,16 +363,16 @@ impl WasiEnv { control_plane: self.control_plane.clone(), process, thread, + layout: self.layout.clone(), vfork: None, poll_seed: 0, - stack_end: self.stack_end, - stack_start: self.stack_start, bin_factory, state, - inner: None, + inner: Default::default(), owned_handles: Vec::new(), runtime: self.runtime.clone(), capabilities: self.capabilities.clone(), + enable_deep_sleep: self.enable_deep_sleep, }; Ok((new_env, handle)) } @@ -371,6 +385,23 @@ impl WasiEnv { self.thread.tid() } + /// Returns true if this module is capable of deep sleep + /// (needs asyncify to unwind and rewin) + pub fn capable_of_deep_sleep(&self) -> bool { + if !self.control_plane.config().enable_asynchronous_threading { + return false; + } + let inner = self.inner(); + inner.asyncify_get_state.is_some() + && inner.asyncify_start_rewind.is_some() + && inner.asyncify_start_unwind.is_some() + } + + /// Returns true if this thread can go into a deep sleep + pub fn layout(&self) -> &WasiMemoryLayout { + &self.layout + } + #[allow(clippy::result_large_err)] pub(crate) fn from_init(init: WasiEnvInit) -> Result { let process = if let Some(p) = init.process { @@ -388,15 +419,15 @@ impl WasiEnv { control_plane: init.control_plane, process, thread: thread.as_thread(), + layout: WasiMemoryLayout::default(), vfork: None, poll_seed: 0, - stack_end: DEFAULT_STACK_SIZE, - stack_start: 0, state: Arc::new(init.state), - inner: None, + inner: Default::default(), owned_handles: Vec::new(), runtime: init.runtime, bin_factory: init.bin_factory, + enable_deep_sleep: init.capabilities.threading.enable_asynchronous_threading, capabilities: init.capabilities, }; env.owned_handles.push(thread); @@ -418,7 +449,7 @@ impl WasiEnv { store: &mut impl AsStoreMut, ) -> Result<(Instance, WasiFunctionEnv), WasiRuntimeError> { let call_initialize = init.call_initialize; - let spawn_type = init.spawn_type.take(); + let spawn_type = init.memory_ty.take(); let env = Self::from_init(init)?; @@ -434,11 +465,11 @@ impl WasiEnv { // Determine if we are going to create memory and import it or just rely on self creation of memory let spawn_type = if let Some(t) = spawn_type { - t + SpawnMemoryType::CreateMemoryOfType(t) } else { match shared_memory { - Some(ty) => SpawnType::CreateWithType(SpawnedMemory { ty }), - None => SpawnType::Create, + Some(ty) => SpawnMemoryType::CreateMemoryOfType(ty), + None => SpawnMemoryType::CreateMemory, } }; let memory = tasks.build_memory(&mut store, spawn_type)?; @@ -471,7 +502,7 @@ impl WasiEnv { // Initialize the WASI environment if let Err(err) = - func_env.initialize_with_memory(&mut store, instance.clone(), imported_memory) + func_env.initialize_with_memory(&mut store, instance.clone(), imported_memory, true) { tracing::error!("wasi[{}]::wasi initialize error ({})", pid, err); func_env @@ -533,9 +564,13 @@ impl WasiEnv { let signals = env.thread.pop_signals(); let signal_cnt = signals.len(); for sig in signals { - if sig == Signal::Sigint || sig == Signal::Sigquit || sig == Signal::Sigkill { - env.thread.set_status_finished(Ok(Errno::Intr.into())); - return Err(WasiError::Exit(Errno::Intr.into())); + if sig == Signal::Sigint + || sig == Signal::Sigquit + || sig == Signal::Sigkill + || sig == Signal::Sigabrt + { + let exit_code = env.thread.set_or_get_exit_code_for_signal(sig); + return Err(WasiError::Exit(exit_code)); } else { trace!("wasi[{}]::signal-ignored: {:?}", env.pid(), sig); } @@ -559,12 +594,6 @@ impl WasiEnv { // differently let env = ctx.data(); if !env.inner().signal_set { - if env - .thread - .has_signal(&[Signal::Sigint, Signal::Sigquit, Signal::Sigkill]) - { - env.thread.set_status_finished(Ok(Errno::Intr.into())); - } return Ok(Ok(false)); } @@ -650,10 +679,16 @@ impl WasiEnv { pub fn should_exit(&self) -> Option { // Check for forced exit if let Some(forced_exit) = self.thread.try_join() { - return Some(forced_exit.unwrap_or_else(|_| Errno::Child.into())); + return Some(forced_exit.unwrap_or_else(|err| { + tracing::debug!("exit runtime error - {}", err); + Errno::Child.into() + })); } if let Some(forced_exit) = self.process.try_join() { - return Some(forced_exit.unwrap_or_else(|_| Errno::Child.into())); + return Some(forced_exit.unwrap_or_else(|err| { + tracing::debug!("exit runtime error - {}", err); + Errno::Child.into() + })); } None } @@ -665,36 +700,57 @@ impl WasiEnv { /// Providers safe access to the initialized part of WasiEnv /// (it must be initialized before it can be used) - pub fn inner(&self) -> &WasiInstanceHandles { - self.inner - .as_ref() - .expect("You must initialize the WasiEnv before using it") + pub(crate) fn inner(&self) -> WasiInstanceGuard<'_> { + self.inner.get().expect( + "You must initialize the WasiEnv before using it and can not pass it between threads", + ) } /// Providers safe access to the initialized part of WasiEnv /// (it must be initialized before it can be used) - pub fn inner_mut(&mut self) -> &mut WasiInstanceHandles { - self.inner - .as_mut() - .expect("You must initialize the WasiEnv before using it") + pub(crate) fn inner_mut(&mut self) -> WasiInstanceGuardMut<'_> { + self.inner.get_mut().expect( + "You must initialize the WasiEnv before using it and can not pass it between threads", + ) + } + + /// Sets the inner object (this should only be called when + /// creating the instance and eventually should be moved out + /// of the WasiEnv) + #[doc(hidden)] + pub(crate) fn set_inner(&mut self, handles: WasiInstanceHandles) { + self.inner.set(handles) + } + + /// Swaps this inner with the WasiEnvironment of another, this + /// is used by the vfork so that the inner handles can be restored + /// after the vfork finishes. + #[doc(hidden)] + pub(crate) fn swap_inner(&mut self, other: &mut Self) { + std::mem::swap(&mut self.inner, &mut other.inner); + } + + /// Tries to clone the instance from this environment + pub fn try_clone_instance(&self) -> Option { + self.inner.get().map(|i| i.instance.clone()) } /// Providers safe access to the memory /// (it must be initialized before it can be used) - pub fn memory_view<'a>(&'a self, store: &'a impl AsStoreRef) -> MemoryView<'a> { - self.memory().view(store) + pub(crate) fn memory(&self) -> WasiInstanceGuardMemory<'_> { + self.inner().memory() } /// Providers safe access to the memory /// (it must be initialized before it can be used) - pub fn memory(&self) -> &Memory { - &self.inner().memory + pub(crate) fn memory_view<'a>(&self, store: &'a (impl AsStoreRef + ?Sized)) -> MemoryView<'a> { + self.memory().view(store) } /// Copy the lazy reference so that when it's initialized during the /// export phase, all the other references get a copy of it - pub fn memory_clone(&self) -> Memory { - self.memory().clone() + pub(crate) fn memory_clone(&self) -> Memory { + self.inner().memory_clone() } /// Get the WASI state diff --git a/lib/wasi/src/state/func_env.rs b/lib/wasi/src/state/func_env.rs index 9c784a8c53a..cb82f69aff3 100644 --- a/lib/wasi/src/state/func_env.rs +++ b/lib/wasi/src/state/func_env.rs @@ -1,13 +1,19 @@ use tracing::trace; -use wasmer::{AsStoreMut, AsStoreRef, ExportError, FunctionEnv, Imports, Instance, Memory, Module}; +use wasmer::{ + AsStoreMut, AsStoreRef, ExportError, FunctionEnv, Imports, Instance, Memory, Module, Store, +}; use wasmer_wasix_types::wasi::ExitCode; use crate::{ + import_object_for_all_wasi_versions, + os::task::thread::DEFAULT_STACK_SIZE, + runtime::SpawnMemoryType, state::WasiInstanceHandles, - utils::{get_wasi_version, get_wasi_versions}, - WasiEnv, WasiError, DEFAULT_STACK_SIZE, + utils::{get_wasi_version, get_wasi_versions, store::restore_snapshot}, + InstanceSnapshot, WasiEnv, WasiError, WasiThreadError, }; +#[derive(Clone)] pub struct WasiFunctionEnv { pub env: FunctionEnv, } @@ -19,6 +25,54 @@ impl WasiFunctionEnv { } } + // Creates a new environment context on a new store + pub fn new_with_store( + module: Module, + env: WasiEnv, + snapshot: Option<&InstanceSnapshot>, + spawn_type: SpawnMemoryType, + update_layout: bool, + ) -> Result<(Self, Store), WasiThreadError> { + // Create a new store and put the memory object in it + // (but only if it has imported memory) + let mut store = env.runtime.new_store(); + let memory = env + .tasks() + .build_memory(&mut store.as_store_mut(), spawn_type)?; + + // Build the context object and import the memory + let mut ctx = WasiFunctionEnv::new(&mut store, env); + let (mut import_object, init) = + import_object_for_all_wasi_versions(&module, &mut store, &ctx.env); + if let Some(memory) = memory.clone() { + import_object.define("env", "memory", memory); + } + + let instance = Instance::new(&mut store, &module, &import_object).map_err(|err| { + tracing::warn!("failed to create instance - {}", err); + WasiThreadError::InstanceCreateFailed(err) + })?; + + init(&instance, &store).map_err(|err| { + tracing::warn!("failed to init instance - {}", err); + WasiThreadError::InitFailed(err) + })?; + + // Initialize the WASI environment + ctx.initialize_with_memory(&mut store, instance, memory, update_layout) + .map_err(|err| { + tracing::warn!("failed initialize environment - {}", err); + WasiThreadError::ExportError(err) + })?; + + // Set all the globals + if let Some(snapshot) = snapshot { + restore_snapshot(&mut store, snapshot); + } + + Ok((ctx, store)) + } + /// Get an `Imports` for a specific version of WASI detected in the module. pub fn import_object( &self, @@ -39,7 +93,7 @@ impl WasiFunctionEnv { } /// Gets a mutable- reference to the host state in this context. - pub fn data_mut<'a>(&'a mut self, store: &'a mut impl AsStoreMut) -> &'a mut WasiEnv { + pub fn data_mut<'a>(&'a self, store: &'a mut impl AsStoreMut) -> &'a mut WasiEnv { self.env.as_mut(store) } @@ -52,7 +106,7 @@ impl WasiFunctionEnv { store: &mut impl AsStoreMut, instance: Instance, ) -> Result<(), ExportError> { - self.initialize_with_memory(store, instance, None) + self.initialize_with_memory(store, instance, None, true) } /// Initializes the WasiEnv using the instance exports and a provided optional memory @@ -64,16 +118,8 @@ impl WasiFunctionEnv { store: &mut impl AsStoreMut, instance: Instance, memory: Option, + update_layout: bool, ) -> Result<(), ExportError> { - // List all the exports and imports - for ns in instance.module().exports() { - //trace!("module::export - {} ({:?})", ns.name(), ns.ty()); - trace!("module::export - {}", ns.name()); - } - for ns in instance.module().imports() { - trace!("module::import - {}::{}", ns.module(), ns.name()); - } - let is_wasix_module = crate::utils::is_wasix_module(instance.module()); // First we get the malloc function which if it exists will be used to @@ -92,21 +138,32 @@ impl WasiFunctionEnv { let new_inner = WasiInstanceHandles::new(memory, store, instance); let env = self.data_mut(store); - env.inner.replace(new_inner); + env.set_inner(new_inner); env.state.fs.set_is_wasix(is_wasix_module); - // Set the base stack - let stack_base = if let Some(stack_pointer) = env.inner().stack_pointer.clone() { - match stack_pointer.get(store) { - wasmer::Value::I32(a) => a as u64, - wasmer::Value::I64(a) => a as u64, - _ => DEFAULT_STACK_SIZE, + // If the stack offset and size is not set then do so + if update_layout { + // Set the base stack + let mut stack_base = if let Some(stack_pointer) = env.inner().stack_pointer.clone() { + match stack_pointer.get(store) { + wasmer::Value::I32(a) => a as u64, + wasmer::Value::I64(a) => a as u64, + _ => 0, + } + } else { + 0 + }; + if stack_base == 0 { + stack_base = DEFAULT_STACK_SIZE; } - } else { - DEFAULT_STACK_SIZE - }; - self.data_mut(store).stack_end = stack_base; + + // Update the stack layout which is need for asyncify + let env = self.data_mut(store); + let layout = &mut env.layout; + layout.stack_upper = stack_base; + layout.stack_size = layout.stack_upper - layout.stack_lower; + } Ok(()) } diff --git a/lib/wasi/src/state/handles/global.rs b/lib/wasi/src/state/handles/global.rs new file mode 100644 index 00000000000..a3313f95a2e --- /dev/null +++ b/lib/wasi/src/state/handles/global.rs @@ -0,0 +1,29 @@ +#![cfg_attr(feature = "js", allow(unused))] +use wasmer::Memory; + +use crate::WasiInstanceHandles; + +pub(crate) type WasiInstanceGuard<'a> = &'a WasiInstanceHandles; +pub(crate) type WasiInstanceGuardMut<'a> = &'a mut WasiInstanceHandles; +pub(crate) type WasiInstanceGuardMemory<'a> = &'a Memory; + +/// This pointer provides global access to some instance handles +#[derive(Debug, Clone, Default)] +pub(crate) struct WasiInstanceHandlesPointer { + inner: Option, +} +impl WasiInstanceHandlesPointer { + pub fn get(&self) -> Option<&WasiInstanceHandles> { + self.inner.as_ref() + } + pub fn get_mut(&mut self) -> Option<&mut WasiInstanceHandles> { + self.inner.as_mut() + } + pub fn set(&mut self, val: WasiInstanceHandles) { + self.inner.replace(val); + } + #[allow(dead_code)] + pub fn clear(&mut self) { + self.inner.take(); + } +} diff --git a/lib/wasi/src/state/handles/mod.rs b/lib/wasi/src/state/handles/mod.rs new file mode 100644 index 00000000000..77a67014e89 --- /dev/null +++ b/lib/wasi/src/state/handles/mod.rs @@ -0,0 +1,7 @@ +mod global; +mod thread_local; + +#[cfg(feature = "sys")] +pub(crate) use global::*; +#[cfg(feature = "js")] +pub(crate) use thread_local::*; diff --git a/lib/wasi/src/state/handles/thread_local.rs b/lib/wasi/src/state/handles/thread_local.rs new file mode 100644 index 00000000000..f7dc80c7e49 --- /dev/null +++ b/lib/wasi/src/state/handles/thread_local.rs @@ -0,0 +1,169 @@ +#![cfg_attr(feature = "sys", allow(unused))] +use std::cell::{Ref, RefCell, RefMut}; +use std::ops::{Deref, DerefMut}; +use std::{ + collections::HashMap, + rc::Rc, + sync::atomic::{AtomicU64, Ordering}, +}; + +use wasmer::Memory; + +use crate::WasiInstanceHandles; + +static LOCAL_INSTANCE_SEED: AtomicU64 = AtomicU64::new(1); +thread_local! { + static THREAD_LOCAL_INSTANCE_HANDLES: RefCell>>> + = RefCell::new(HashMap::new()); +} + +/// This non-sendable guard provides memory safe access +/// to the WasiInstance object but only when it is +/// constructed with certain constraints +pub(crate) struct WasiInstanceGuard<'a> { + // the order is very important as the first value is + // dropped before the reference count is dropped + borrow: Ref<'static, WasiInstanceHandles>, + _pointer: &'a WasiInstanceHandlesPointer, + _inner: Rc>, +} +impl<'a> Deref for WasiInstanceGuard<'a> { + type Target = WasiInstanceHandles; + fn deref(&self) -> &Self::Target { + self.borrow.deref() + } +} + +/// This non-sendable guard provides memory safe access +/// to the WasiInstance object but only when it is +/// constructed with certain constraints. This one provides +/// mutable access +pub(crate) struct WasiInstanceGuardMut<'a> { + // the order is very important as the first value is + // dropped before the reference count is dropped + borrow: RefMut<'static, WasiInstanceHandles>, + _pointer: &'a WasiInstanceHandlesPointer, + _inner: Rc>, +} +impl<'a> Deref for WasiInstanceGuardMut<'a> { + type Target = WasiInstanceHandles; + + fn deref(&self) -> &Self::Target { + self.borrow.deref() + } +} +impl<'a> DerefMut for WasiInstanceGuardMut<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.borrow.deref_mut() + } +} + +/// This handle protects the WasiInstance and makes it +/// accessible only when you are in the current thread +/// otherwise it will return None. This means it becomes +/// possible to make WasiEnv send without unsafe code +/// however it means that access to the must be checked +#[derive(Debug, Default, Clone)] +pub(crate) struct WasiInstanceHandlesPointer { + /// Inner functions and references that are loaded before the environment starts + id: Option, +} +impl Drop for WasiInstanceHandlesPointer { + fn drop(&mut self) { + self.clear(); + } +} +impl WasiInstanceHandlesPointer { + pub fn get(&self) -> Option> { + self.id + .iter() + .filter_map(|id| { + THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { + let map = map.borrow(); + if let Some(inner) = map.get(id) { + let borrow: Ref = inner.borrow(); + let borrow: Ref<'static, WasiInstanceHandles> = + unsafe { std::mem::transmute(borrow) }; + Some(WasiInstanceGuard { + borrow, + _pointer: self, + _inner: inner.clone(), + }) + } else { + None + } + }) + }) + .next() + } + pub fn get_mut(&self) -> Option> { + self.id + .into_iter() + .filter_map(|id| { + THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { + let map = map.borrow_mut(); + if let Some(inner) = map.get(&id) { + let borrow: RefMut = inner.borrow_mut(); + let borrow: RefMut<'static, WasiInstanceHandles> = + unsafe { std::mem::transmute(borrow) }; + Some(WasiInstanceGuardMut { + borrow, + _pointer: self, + _inner: inner.clone(), + }) + } else { + None + } + }) + }) + .next() + } + pub fn set(&mut self, val: WasiInstanceHandles) { + self.clear(); + + let id = LOCAL_INSTANCE_SEED.fetch_add(1, Ordering::SeqCst); + THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { + let mut map = map.borrow_mut(); + map.insert(id, Rc::new(RefCell::new(val))); + }); + if let Some(old_id) = self.id.replace(id) { + Self::destroy(old_id) + } + } + #[allow(dead_code)] + pub fn clear(&mut self) { + if let Some(id) = self.id.take() { + Self::destroy(id) + } + } + fn destroy(id: u64) { + THREAD_LOCAL_INSTANCE_HANDLES.with(|map| { + let mut map = map.borrow_mut(); + map.remove(&id); + }) + } +} + +/// This provides access to the memory inside the instance +pub(crate) struct WasiInstanceGuardMemory<'a> { + // the order is very important as the first value is + // dropped before the reference count is dropped + borrow: &'a Memory, + _guard: WasiInstanceGuard<'a>, +} +impl<'a> Deref for WasiInstanceGuardMemory<'a> { + type Target = Memory; + fn deref(&self) -> &Self::Target { + self.borrow + } +} +impl<'a> WasiInstanceGuard<'a> { + pub fn memory(self) -> WasiInstanceGuardMemory<'a> { + let borrow: &Memory = &self.memory; + let borrow: &'a Memory = unsafe { std::mem::transmute(borrow) }; + WasiInstanceGuardMemory { + borrow, + _guard: self, + } + } +} diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index ef221e3035f..0a354f9c30d 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -18,22 +18,20 @@ mod builder; mod env; mod func_env; +mod handles; mod types; use std::{ - cell::RefCell, - collections::HashMap, + collections::{BTreeMap, HashMap}, path::Path, - sync::{Arc, Mutex, RwLock}, + sync::Mutex, task::Waker, time::Duration, }; -use derivative::Derivative; #[cfg(feature = "enable-serde")] use serde::{Deserialize, Serialize}; use virtual_fs::{FileOpener, FileSystem, FsError, OpenOptions, VirtualFile}; -use wasmer::Store; use wasmer_wasix_types::wasi::{Errno, Fd as WasiFd, Rights, Snapshot0Clockid}; pub use self::{ @@ -47,8 +45,8 @@ use crate::{ fs::{fs_error_into_wasi_err, WasiFs, WasiFsRoot, WasiInodes, WasiStateFileGuard}, syscalls::types::*, utils::WasiParkingLot, - WasiCallingId, }; +pub(crate) use handles::*; /// all the rights enabled pub const ALL_RIGHTS: Rights = Rights::all(); @@ -69,38 +67,11 @@ impl FileOpener for WasiStateOpener { } } -// TODO: review allow... -#[allow(dead_code)] -pub(crate) struct WasiThreadContext { - pub ctx: WasiFunctionEnv, - pub store: RefCell, -} - -/// The code itself makes safe use of the struct so multiple threads don't access -/// it (without this the JS code prevents the reference to the module from being stored -/// which is needed for the multithreading mode) -unsafe impl Send for WasiThreadContext {} -unsafe impl Sync for WasiThreadContext {} - -/// Structures used for the threading and sub-processes -/// -/// These internal implementation details are hidden away from the -/// consumer who should instead implement the vbus trait on the runtime -#[derive(Derivative, Default)] -// TODO: review allow... -#[allow(dead_code)] -#[derivative(Debug)] -#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] -pub(crate) struct WasiStateThreading { - #[derivative(Debug = "ignore")] - pub thread_ctx: HashMap>, -} - /// Represents a futex which will make threads wait for completion in a more /// CPU efficient manner -#[derive(Debug)] +#[derive(Debug, Default)] pub struct WasiFutex { - pub(crate) wakers: Vec, + pub(crate) wakers: BTreeMap>, } /// Structure that holds the state of BUS calls to this process and from @@ -136,6 +107,14 @@ impl WasiBusState { } } +/// Stores the state of the futexes +#[derive(Debug, Default)] +#[cfg_attr(feature = "enable-serde", derive(Serialize, Deserialize))] +pub(crate) struct WasiFutexState { + pub poller_seed: u64, + pub futexes: HashMap, +} + /// Top level data type containing all* the state with which WASI can /// interact. /// @@ -149,8 +128,7 @@ pub(crate) struct WasiState { pub fs: WasiFs, pub inodes: WasiInodes, - pub threading: RwLock, - pub futexs: Mutex>, + pub futexs: Mutex, pub clock_offset: Mutex>, pub args: Vec, pub envs: Vec>, @@ -270,7 +248,6 @@ impl WasiState { fs: self.fs.fork(), secret: self.secret, inodes: self.inodes.clone(), - threading: Default::default(), futexs: Default::default(), clock_offset: Mutex::new(self.clock_offset.lock().unwrap().clone()), args: self.args.clone(), diff --git a/lib/wasi/src/syscalls/legacy/snapshot0.rs b/lib/wasi/src/syscalls/legacy/snapshot0.rs index 33e27ff1a53..7afa0e52531 100644 --- a/lib/wasi/src/syscalls/legacy/snapshot0.rs +++ b/lib/wasi/src/syscalls/legacy/snapshot0.rs @@ -1,5 +1,5 @@ use tracing::{field, instrument, trace_span}; -use wasmer::{AsStoreMut, FunctionEnvMut, WasmPtr}; +use wasmer::{AsStoreMut, AsStoreRef, FunctionEnvMut, Memory, WasmPtr}; use wasmer_wasix_types::wasi::{ Errno, Event, EventFdReadwrite, Eventrwflags, Eventtype, Fd, Filesize, Filestat, Filetype, Snapshot0Event, Snapshot0Filestat, Snapshot0Subscription, Snapshot0Whence, Subscription, @@ -10,8 +10,8 @@ use crate::{ mem_error_to_wasi, os::task::thread::WasiThread, state::{PollEventBuilder, PollEventSet}, - syscalls, syscalls::types, + syscalls::{self, handle_rewind}, Memory32, MemorySize, WasiEnv, WasiError, }; @@ -68,14 +68,19 @@ pub fn fd_seek( /// Wrapper around `syscalls::poll_oneoff` with extra logic to add the removed /// userdata field back -#[instrument(level = "trace", skip_all, fields(timeout_ns = field::Empty, fd_guards = field::Empty, seen = field::Empty), ret, err)] -pub fn poll_oneoff( +#[instrument(level = "trace", skip_all, fields(timeout_ms = field::Empty, fd_guards = field::Empty, seen = field::Empty), ret, err)] +pub fn poll_oneoff( mut ctx: FunctionEnvMut, in_: WasmPtr, out_: WasmPtr, nsubscriptions: u32, nevents: WasmPtr, ) -> Result { + wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + if handle_rewind::(&mut ctx) { + return Ok(Errno::Success); + } + let env = ctx.data(); let memory = env.memory_view(&ctx); let mut subscriptions = Vec::new(); @@ -89,39 +94,38 @@ pub fn poll_oneoff( )); } - // make the call - let triggered_events = syscalls::poll_oneoff_internal(&mut ctx, subscriptions)?; - let triggered_events = match triggered_events { - Ok(a) => a, - Err(err) => { - tracing::trace!(err = err as u16); - return Ok(err); + // We clear the number of events + wasi_try_mem_ok!(nevents.write(&memory, 0)); + + // Function to invoke once the poll is finished + let process_events = move |memory: &'_ Memory, store: &'_ dyn AsStoreRef, triggered_events| { + // Process all the events that were triggered + let mut view = memory.view(store); + let mut events_seen: u32 = 0; + let event_array = wasi_try_mem!(out_.slice(&view, nsubscriptions)); + for event in triggered_events { + let event: Event = event; + let event = Snapshot0Event { + userdata: event.userdata, + error: event.error, + type_: Eventtype::FdRead, + fd_readwrite: match event.type_ { + Eventtype::FdRead => unsafe { event.u.fd_readwrite }, + Eventtype::FdWrite => unsafe { event.u.fd_readwrite }, + Eventtype::Clock => EventFdReadwrite { + nbytes: 0, + flags: Eventrwflags::empty(), + }, + }, + }; + wasi_try_mem!(event_array.index(events_seen as u64).write(event)); + events_seen += 1; } + let out_ptr = nevents.deref(&view); + wasi_try_mem!(out_ptr.write(events_seen)); + Errno::Success }; - // Process all the events that were triggered - let mut env = ctx.data(); - let mut memory = env.memory_view(&ctx); - let mut events_seen: u32 = 0; - let event_array = wasi_try_mem_ok!(out_.slice(&memory, nsubscriptions)); - for event in triggered_events { - let event = Snapshot0Event { - userdata: event.userdata, - error: event.error, - type_: Eventtype::FdRead, - fd_readwrite: match event.type_ { - Eventtype::FdRead => unsafe { event.u.fd_readwrite }, - Eventtype::FdWrite => unsafe { event.u.fd_readwrite }, - Eventtype::Clock => EventFdReadwrite { - nbytes: 0, - flags: Eventrwflags::empty(), - }, - }, - }; - wasi_try_mem_ok!(event_array.index(events_seen as u64).write(event)); - events_seen += 1; - } - let out_ptr = nevents.deref(&memory); - wasi_try_mem_ok!(out_ptr.write(events_seen)); - Ok(Errno::Success) + // Poll and receive all the events that triggered + syscalls::poll_oneoff_internal::(ctx, subscriptions, process_events) } diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index f76e6b5370f..70d6877c99d 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -47,7 +47,7 @@ pub(crate) use std::{ thread::LocalKey, time::Duration, }; -use std::{io::IoSlice, mem::MaybeUninit}; +use std::{io::IoSlice, mem::MaybeUninit, time::Instant}; pub(crate) use bytes::{Bytes, BytesMut}; pub(crate) use cooked_waker::IntoWaker; @@ -87,28 +87,27 @@ pub(crate) use self::types::{ }, *, }; -use self::utils::WasiDummyWaker; +use self::{state::WasiInstanceGuardMemory, utils::WasiDummyWaker}; pub(crate) use crate::os::task::{ process::{WasiProcessId, WasiProcessWait}, thread::{WasiThread, WasiThreadId}, }; pub(crate) use crate::{ bin_factory::spawn_exec_module, - current_caller_id, import_object_for_all_wasi_versions, mem_error_to_wasi, + import_object_for_all_wasi_versions, mem_error_to_wasi, net::{ read_ip_port, socket::{InodeHttpSocketType, InodeSocket, InodeSocketKind}, write_ip_port, }, - runtime::{task_manager::VirtualTaskManagerExt, SpawnType}, + runtime::{task_manager::VirtualTaskManagerExt, SpawnMemoryType}, state::{ self, bus_errno_into_vbus_error, iterate_poll_events, vbus_error_into_bus_errno, InodeGuard, InodeWeakGuard, PollEvent, PollEventBuilder, WasiFutex, WasiState, - WasiThreadContext, }, utils::{self, map_io_err}, VirtualTaskManager, WasiEnv, WasiError, WasiFunctionEnv, WasiInstanceHandles, WasiRuntime, - WasiVFork, DEFAULT_STACK_SIZE, + WasiVFork, }; use crate::{ fs::{ @@ -116,7 +115,7 @@ use crate::{ MAX_SYMLINKS, }, utils::store::InstanceSnapshot, - VirtualBusError, WasiInodes, + DeepSleepWork, RewindPostProcess, RewindState, VirtualBusError, WasiInodes, }; pub(crate) use crate::{net::net_error_into_wasi_err, utils::WasiParkingLot}; @@ -227,6 +226,55 @@ pub async fn stderr_write(ctx: &FunctionEnvMut<'_, WasiEnv>, buf: &[u8]) -> Resu stderr.write_all(buf).await.map_err(map_io_err) } +fn block_on_with_timeout( + tasks: &Arc, + timeout: Option, + work: Fut, +) -> Result, WasiError> +where + Fut: Future, WasiError>>, +{ + let mut nonblocking = false; + if timeout == Some(Duration::ZERO) { + nonblocking = true; + } + let timeout = async { + if let Some(timeout) = timeout { + if !nonblocking { + tasks.sleep_now(timeout).await + } else { + InfiniteSleep::default().await + } + } else { + InfiniteSleep::default().await + } + }; + + let work = async move { + tokio::select! { + // The main work we are doing + res = work => res, + // Optional timeout + _ = timeout => Ok(Err(Errno::Timedout)), + } + }; + + // Fast path + if nonblocking { + let waker = WasiDummyWaker.into_waker(); + let mut cx = Context::from_waker(&waker); + let _guard = tasks.runtime_enter(); + let mut pinned_work = Box::pin(work); + if let Poll::Ready(res) = pinned_work.as_mut().poll(&mut cx) { + return res; + } + return Ok(Err(Errno::Again)); + } + + // Slow path, block on the work and process process + tasks.block_on(work) +} + /// Asyncify takes the current thread and blocks on the async runtime associated with it /// thus allowed for asynchronous operations to execute. It has built in functionality /// to (optionally) timeout the IO, force exit the process, callback signals and pump @@ -247,35 +295,15 @@ where return Err(WasiError::Exit(exit_code)); } - // Create the timeout - let mut nonblocking = false; - if timeout == Some(Duration::ZERO) { - nonblocking = true; - } - let timeout = { - let tasks_inner = env.tasks().clone(); - async move { - if let Some(timeout) = timeout { - if !nonblocking { - tasks_inner.sleep_now(timeout).await - } else { - InfiniteSleep::default().await - } - } else { - InfiniteSleep::default().await - } - } - }; - // This poller will process any signals when the main working function is idle - struct WorkWithSignalPoller<'a, 'b, Fut, T> + struct Poller<'a, 'b, Fut, T> where Fut: Future>, { ctx: &'a mut FunctionEnvMut<'b, WasiEnv>, pinned_work: Pin>, } - impl<'a, 'b, Fut, T> Future for WorkWithSignalPoller<'a, 'b, Fut, T> + impl<'a, 'b, Fut, T> Future for Poller<'a, 'b, Fut, T> where Fut: Future>, { @@ -297,32 +325,241 @@ where } } - // Define the work function - let tasks = env.tasks().clone(); + // Block on the work let mut pinned_work = Box::pin(work); - let work = async { + let tasks = env.tasks().clone(); + let poller = Poller { ctx, pinned_work }; + block_on_with_timeout(&tasks, timeout, poller) +} + +/// Future that will be polled by asyncify methods +pub type AsyncifyFuture = dyn Future> + Send + Sync + 'static; + +// This poller will process any signals when the main working function is idle +struct AsyncifyPoller<'a, 'b, 'c> { + signal_set: bool, + thread: WasiThread, + memory: WasiInstanceGuardMemory<'a>, + store: &'b dyn AsStoreRef, + work: &'c mut Pin>, +} +impl<'a, 'b, 'c> Future for AsyncifyPoller<'a, 'b, 'c> { + type Output = Result, WasiError>; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let store = self.store; + let memory = self.memory.clone(); + if let Poll::Ready(res) = self.work.as_mut().poll(cx) { + return Poll::Ready(Ok(res)); + } + if let Some(forced_exit) = self.thread.try_join() { + return Poll::Ready(Err(WasiError::Exit(forced_exit.unwrap_or_else(|err| { + tracing::debug!("exit runtime error - {}", err); + Errno::Child.into() + })))); + } + if !self.signal_set && self.thread.has_signals_or_subscribe(cx.waker()) { + let signals = self.thread.signals().lock().unwrap(); + for sig in signals.0.iter() { + if *sig == Signal::Sigint + || *sig == Signal::Sigquit + || *sig == Signal::Sigkill + || *sig == Signal::Sigabrt + { + let exit_code = self.thread.set_or_get_exit_code_for_signal(*sig); + return Poll::Ready(Err(WasiError::Exit(exit_code))); + } + } + return Poll::Ready(Ok(Err(Errno::Intr))); + } + Poll::Pending + } +} + +pub enum AsyncifyAction<'a> { + /// Indicates that asyncify callback finished and the + /// caller now has ownership of the ctx again + Finish(FunctionEnvMut<'a, WasiEnv>), + /// Indicates that asyncify should unwind by immediately exiting + /// the current function + Unwind, +} + +/// Asyncify takes the current thread and blocks on the async runtime associated with it +/// thus allowed for asynchronous operations to execute. It has built in functionality +/// to (optionally) timeout the IO, force exit the process, callback signals and pump +/// synchronous IO engine +/// +/// This will either return the `ctx` as the asyncify has completed successfully +/// or it will return an WasiError which will exit the WASM call using asyncify +/// and instead process it on a shared task +/// +pub(crate) fn __asyncify_with_deep_sleep( + ctx: FunctionEnvMut<'_, WasiEnv>, + timeout: Option, + deep_sleep_time: Duration, + work: Pin>, + mut after: After, +) -> Result, WasiError> +where + After: RewindPostProcess + Send + Sync + 'static, +{ + // We build a wrapper around the polling struct that will + // check for a timeout + struct WorkWithTimeout { + inner: Pin>, + timeout: Option + Send + Sync>>>, + } + impl Future for WorkWithTimeout { + type Output = Result<(), Errno>; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let inner = self.inner.as_mut(); + if let Poll::Ready(res) = inner.poll(cx) { + return Poll::Ready(res); + } + if let Some(timeout) = self.timeout.as_mut() { + if timeout.as_mut().poll(cx).is_ready() { + self.timeout.take(); + return Poll::Ready(Err(Errno::Timedout)); + } + } + + Poll::Pending + } + } + let work = Box::pin(WorkWithTimeout { + inner: work, + timeout: timeout.map(|timeout| ctx.data().tasks().sleep_now(timeout)), + }); + let mut work: Pin> = work; + + // Define the work + let tasks = ctx.data().tasks().clone(); + let work = async move { + let env = ctx.data(); + + // Create the deep sleeper + let deep_sleep_wait = async { + if env.enable_deep_sleep { + env.tasks().sleep_now(deep_sleep_time).await + } else { + InfiniteSleep::default().await + } + }; + Ok(tokio::select! { - // The main work we are doing - res = WorkWithSignalPoller { ctx, pinned_work } => res?, - // Optional timeout - _ = timeout => Err(Errno::Timedout), + // Inner wait with finializer + res = AsyncifyPoller { + signal_set: ctx.data().inner().signal_set, + thread: ctx.data().thread.clone(), + memory: ctx.data().memory(), + store: &ctx, + work: &mut work, + } => { + after.finish(ctx.data(), &ctx, res?); + AsyncifyAction::Finish(ctx) + }, + // Determines when and if we should go into a deep sleep + _ = deep_sleep_wait => { + let pid = ctx.data().pid(); + let tid = ctx.data().tid(); + tracing::trace!(%pid, %tid, "thread entering deep sleep"); + deep_sleep::(ctx, work, after)?; + AsyncifyAction::Unwind + }, }) }; - // Fast path - if nonblocking { - let waker = WasiDummyWaker.into_waker(); - let mut cx = Context::from_waker(&waker); - let _guard = tasks.runtime_enter(); - let mut pinned_work = Box::pin(work); - if let Poll::Ready(res) = pinned_work.as_mut().poll(&mut cx) { - return res; + // Block on the work + tasks.block_on(work) +} + +pub(crate) type AsyncifyWorkAfter = dyn FnOnce(&Memory, &dyn AsStoreRef, Result) -> Result<(), ExitCode> + + Send + + Sync + + 'static; + +/// Asyncify takes the current thread and blocks on the async runtime associated with it +/// thus allowed for asynchronous operations to execute. It has built in functionality +/// to (optionally) timeout the IO, force exit the process, callback signals and pump +/// synchronous IO engine +/// +/// This will either return the `ctx` as the asyncify has completed successfully +/// or it will return an WasiError which will exit the WASM call using asyncify +/// and instead process it on a shared task +/// +pub(crate) fn __asyncify_with_deep_sleep_ext( + ctx: FunctionEnvMut<'_, WasiEnv>, + timeout: Option, + deep_sleep_time: Duration, + trigger: Fut, + after: After, +) -> Result, WasiError> +where + T: Send + Sync + 'static, + Fut: Future + Send + Sync + 'static, + After: FnOnce(&Memory, &dyn AsStoreRef, Result) -> Result<(), ExitCode> + + Send + + Sync + + 'static, +{ + let ret = Arc::new(Mutex::new(None)); + struct Poller { + work: Pin + Send + Sync + 'static>>, + ret: Arc>>, + } + impl Future for Poller { + type Output = Result<(), Errno>; + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.work.as_mut().poll(cx) { + Poll::Ready(ret) => { + let mut guard = self.ret.lock().unwrap(); + guard.replace(ret); + Poll::Ready(Ok(())) + } + Poll::Pending => Poll::Pending, + } + } + } + struct Finisher { + after: Option>>, + ret: Arc>>, + } + impl RewindPostProcess for Finisher { + fn finish( + &mut self, + env: &WasiEnv, + store: &dyn AsStoreRef, + res: Result<(), Errno>, + ) -> Result<(), ExitCode> { + if let Some(after) = self.after.take() { + let res = match res { + Ok(()) => { + let mut guard = self.ret.lock().unwrap(); + guard.take().ok_or(Errno::Unknown) + } + Err(err) => Err(err), + }; + let memory = env.memory(); + after(memory.deref(), store, res) + } else { + Err(ExitCode::Errno(Errno::Unknown)) + } } - return Ok(Err(Errno::Again)); } - // Slow path, block on the work and process process - tasks.block_on(work) + __asyncify_with_deep_sleep::( + ctx, + timeout, + deep_sleep_time, + Box::pin(Poller:: { + work: Box::pin(trigger), + ret: ret.clone(), + }), + Finisher { + after: Some(Box::new(after)), + ret, + }, + ) } /// Asyncify takes the current thread and blocks on the async runtime associated with it @@ -336,36 +573,17 @@ pub(crate) fn __asyncify_light( ) -> Result, WasiError> where T: 'static, - Fut: std::future::Future>, + Fut: Future>, { - // Create the timeout - let mut nonblocking = false; - if timeout == Some(Duration::ZERO) { - nonblocking = true; - } - let timeout = { - async { - if let Some(timeout) = timeout { - if !nonblocking { - env.tasks().sleep_now(timeout).await - } else { - InfiniteSleep::default().await - } - } else { - InfiniteSleep::default().await - } - } - }; - // This poller will process any signals when the main working function is idle - struct WorkWithSignalPoller<'a, Fut, T> + struct Poller<'a, Fut, T> where Fut: Future>, { env: &'a WasiEnv, pinned_work: Pin>, } - impl<'a, Fut, T> Future for WorkWithSignalPoller<'a, Fut, T> + impl<'a, Fut, T> Future for Poller<'a, Fut, T> where Fut: Future>, { @@ -384,38 +602,17 @@ where } } - // Define the work function + // Block on the work let mut pinned_work = Box::pin(work); - let work = async move { - Ok(tokio::select! { - // The main work we are doing - res = WorkWithSignalPoller { env, pinned_work } => res?, - // Optional timeout - _ = timeout => Err(Errno::Timedout), - }) - }; - - // Fast path - if nonblocking { - let waker = WasiDummyWaker.into_waker(); - let mut cx = Context::from_waker(&waker); - let _guard = env.tasks().runtime_enter(); - let mut pinned_work = Box::pin(work); - if let Poll::Ready(res) = pinned_work.as_mut().poll(&mut cx) { - return res; - } - return Ok(Err(Errno::Again)); - } - - // Slow path, block on the work and process process - env.tasks().block_on(work) + let poller = Poller { env, pinned_work }; + block_on_with_timeout(env.tasks(), timeout, poller) } // This should be compiled away, it will simply wait forever however its never // used by itself, normally this is passed into asyncify which will still abort // the operating on timeouts, signals or other work due to a select! around the await #[derive(Default)] -struct InfiniteSleep {} +pub struct InfiniteSleep {} impl std::future::Future for InfiniteSleep { type Output = (); fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { @@ -689,12 +886,12 @@ pub(crate) fn get_current_time_in_nanos() -> Result { Ok(now as Timestamp) } -pub(crate) fn get_stack_base(mut ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> u64 { - ctx.data().stack_end +pub(crate) fn get_stack_lower(env: &WasiEnv) -> u64 { + env.layout.stack_lower } -pub(crate) fn get_stack_start(mut ctx: &mut FunctionEnvMut<'_, WasiEnv>) -> u64 { - ctx.data().stack_start +pub(crate) fn get_stack_upper(env: &WasiEnv) -> u64 { + env.layout.stack_upper } pub(crate) fn get_memory_stack_pointer( @@ -702,12 +899,12 @@ pub(crate) fn get_memory_stack_pointer( ) -> Result { // Get the current value of the stack pointer (which we will use // to save all of the stack) - let stack_base = get_stack_base(ctx); + let stack_upper = get_stack_upper(ctx.data()); let stack_pointer = if let Some(stack_pointer) = ctx.data().inner().stack_pointer.clone() { match stack_pointer.get(ctx) { Value::I32(a) => a as u64, Value::I64(a) => a as u64, - _ => stack_base, + _ => stack_upper, } } else { return Err("failed to save stack: not exported __stack_pointer global".to_string()); @@ -718,25 +915,26 @@ pub(crate) fn get_memory_stack_pointer( pub(crate) fn get_memory_stack_offset( ctx: &mut FunctionEnvMut<'_, WasiEnv>, ) -> Result { - let stack_base = get_stack_base(ctx); + let stack_upper = get_stack_upper(ctx.data()); let stack_pointer = get_memory_stack_pointer(ctx)?; - Ok(stack_base - stack_pointer) + Ok(stack_upper - stack_pointer) } pub(crate) fn set_memory_stack_offset( - ctx: &mut FunctionEnvMut<'_, WasiEnv>, + env: &WasiEnv, + store: &mut impl AsStoreMut, offset: u64, ) -> Result<(), String> { // Sets the stack pointer - let stack_base = get_stack_base(ctx); - let stack_pointer = stack_base - offset; - if let Some(stack_pointer_ptr) = ctx.data().inner().stack_pointer.clone() { - match stack_pointer_ptr.get(ctx) { + let stack_upper = get_stack_upper(env); + let stack_pointer = stack_upper - offset; + if let Some(stack_pointer_ptr) = env.inner().stack_pointer.clone() { + match stack_pointer_ptr.get(store) { Value::I32(_) => { - stack_pointer_ptr.set(ctx, Value::I32(stack_pointer as i32)); + stack_pointer_ptr.set(store, Value::I32(stack_pointer as i32)); } Value::I64(_) => { - stack_pointer_ptr.set(ctx, Value::I64(stack_pointer as i64)); + stack_pointer_ptr.set(store, Value::I64(stack_pointer as i64)); } _ => { return Err( @@ -753,13 +951,14 @@ pub(crate) fn set_memory_stack_offset( #[allow(dead_code)] pub(crate) fn get_memory_stack( - ctx: &mut FunctionEnvMut<'_, WasiEnv>, + env: &WasiEnv, + store: &mut impl AsStoreMut, ) -> Result { // Get the current value of the stack pointer (which we will use // to save all of the stack) - let stack_base = get_stack_base(ctx); - let stack_pointer = if let Some(stack_pointer) = ctx.data().inner().stack_pointer.clone() { - match stack_pointer.get(ctx) { + let stack_base = get_stack_upper(env); + let stack_pointer = if let Some(stack_pointer) = env.inner().stack_pointer.clone() { + match stack_pointer.get(store) { Value::I32(a) => a as u64, Value::I64(a) => a as u64, _ => stack_base, @@ -767,15 +966,14 @@ pub(crate) fn get_memory_stack( } else { return Err("failed to save stack: not exported __stack_pointer global".to_string()); }; - let env = ctx.data(); - let memory = env.memory_view(&ctx); - let stack_offset = env.stack_end - stack_pointer; + let memory = env.memory_view(store); + let stack_offset = env.layout.stack_upper - stack_pointer; // Read the memory stack into a vector let memory_stack_ptr = WasmPtr::::new( stack_pointer .try_into() - .map_err(|_| "failed to save stack: stack pointer overflow".to_string())?, + .map_err(|err| format!("failed to save stack: stack pointer overflow (stack_pointer={}, stack_lower={}, stack_upper={})", stack_offset, env.layout.stack_lower, env.layout.stack_upper))?, ); memory_stack_ptr @@ -783,7 +981,7 @@ pub(crate) fn get_memory_stack( &memory, stack_offset .try_into() - .map_err(|_| "failed to save stack: stack pointer overflow".to_string())?, + .map_err(|err| format!("failed to save stack: stack pointer overflow (stack_pointer={}, stack_lower={}, stack_upper={})", stack_offset, env.layout.stack_lower, env.layout.stack_upper))?, ) .and_then(|memory_stack| memory_stack.read_to_bytes()) .map_err(|err| format!("failed to read stack: {}", err)) @@ -791,21 +989,21 @@ pub(crate) fn get_memory_stack( #[allow(dead_code)] pub(crate) fn set_memory_stack( - mut ctx: &mut FunctionEnvMut<'_, WasiEnv>, + env: &WasiEnv, + store: &mut impl AsStoreMut, stack: Bytes, ) -> Result<(), String> { // First we restore the memory stack - let stack_base = get_stack_base(ctx); + let stack_upper = get_stack_upper(env); let stack_offset = stack.len() as u64; - let stack_pointer = stack_base - stack_offset; + let stack_pointer = stack_upper - stack_offset; let stack_ptr = WasmPtr::::new( stack_pointer .try_into() .map_err(|_| "failed to restore stack: stack pointer overflow".to_string())?, ); - let env = ctx.data(); - let memory = env.memory_view(&ctx); + let memory = env.memory_view(store); stack_ptr .slice( &memory, @@ -817,12 +1015,54 @@ pub(crate) fn set_memory_stack( .map_err(|err| format!("failed to write stack: {}", err))?; // Set the stack pointer itself and return - set_memory_stack_offset(ctx, stack_offset)?; + set_memory_stack_offset(env, store, stack_offset)?; Ok(()) } +/// Puts the process to deep sleep and wakes it again when +/// the supplied future completes #[must_use = "you must return the result immediately so the stack can unwind"] -pub(crate) fn unwind( +pub(crate) fn deep_sleep( + mut ctx: FunctionEnvMut<'_, WasiEnv>, + trigger: Pin>, + after: After, +) -> Result<(), WasiError> +where + After: RewindPostProcess + Send + Sync + 'static, +{ + // Grab all the globals and serialize them + let store_data = crate::utils::store::capture_snapshot(&mut ctx.as_store_mut()) + .serialize() + .unwrap(); + let store_data = Bytes::from(store_data); + + // Perform the unwind action + let tasks = ctx.data().tasks().clone(); + let res = unwind::(ctx, move |_ctx, memory_stack, rewind_stack| { + // Schedule the process on the stack so that it can be resumed + OnCalledAction::Trap(Box::new(RuntimeError::user(Box::new( + WasiError::DeepSleep(DeepSleepWork { + trigger, + rewind: RewindState { + memory_stack: memory_stack.freeze(), + rewind_stack: rewind_stack.freeze(), + store_data, + is_64bit: M::is_64bit(), + finish: Box::new(after), + }, + }), + )))) + })?; + + // If there is an error then exit the process, otherwise we are done + match res { + Errno::Success => Ok(()), + err => Err(WasiError::Exit(ExitCode::Errno(err))), + } +} + +#[must_use = "you must return the result immediately so the stack can unwind"] +pub fn unwind( mut ctx: FunctionEnvMut<'_, WasiEnv>, callback: F, ) -> Result @@ -834,7 +1074,8 @@ where { // Get the current stack pointer (this will be used to determine the // upper limit of stack space remaining to unwind into) - let memory_stack = match get_memory_stack::(&mut ctx) { + let (env, mut store) = ctx.data_and_store_mut(); + let memory_stack = match get_memory_stack::(env, &mut store) { Ok(a) => a, Err(err) => { warn!("unable to get the memory stack - {}", err); @@ -847,12 +1088,16 @@ where let memory = env.memory_view(&ctx); // Write the addresses to the start of the stack space - let unwind_pointer = env.stack_start; + let unwind_pointer = env.layout.stack_lower; let unwind_data_start = unwind_pointer + (std::mem::size_of::<__wasi_asyncify_t>() as u64); let unwind_data = __wasi_asyncify_t:: { start: wasi_try_ok!(unwind_data_start.try_into().map_err(|_| Errno::Overflow)), - end: wasi_try_ok!(env.stack_end.try_into().map_err(|_| Errno::Overflow)), + end: wasi_try_ok!(env + .layout + .stack_upper + .try_into() + .map_err(|_| Errno::Overflow)), }; let unwind_data_ptr: WasmPtr<__wasi_asyncify_t, M> = WasmPtr::new(wasi_try_ok!(unwind_pointer @@ -873,11 +1118,11 @@ where // Set callback that will be invoked when this process finishes let env = ctx.data(); let unwind_stack_begin: u64 = unwind_data.start.into(); - let total_stack_space = env.stack_end - env.stack_start; + let total_stack_space = env.layout.stack_size; let func = ctx.as_ref(); trace!( - stack_end = env.stack_end, - stack_start = env.stack_start, + stack_upper = env.layout.stack_upper, + stack_lower = env.layout.stack_lower, "wasi[{}:{}]::unwinding (used_stack_space={} total_stack_space={})", ctx.data().pid(), ctx.data().tid(), @@ -939,8 +1184,8 @@ where #[instrument(level = "debug", skip_all, fields(memory_stack_len = memory_stack.len(), rewind_stack_len = rewind_stack.len(), store_data_len = store_data.len()))] #[must_use = "the action must be passed to the call loop"] -pub(crate) fn rewind( - mut ctx: FunctionEnvMut<'_, WasiEnv>, +pub fn rewind( + mut ctx: FunctionEnvMut, memory_stack: Bytes, rewind_stack: Bytes, store_data: Bytes, @@ -956,25 +1201,29 @@ pub(crate) fn rewind( return Errno::Unknown; } }; - crate::utils::store::restore_snapshot(&mut ctx.as_store_mut(), &store_snapshot); + crate::utils::store::restore_snapshot(&mut ctx, &store_snapshot); let env = ctx.data(); let memory = env.memory_view(&ctx); // Write the addresses to the start of the stack space - let rewind_pointer = env.stack_start; + let rewind_pointer = env.layout.stack_lower; let rewind_data_start = rewind_pointer + (std::mem::size_of::<__wasi_asyncify_t>() as u64); let rewind_data_end = rewind_data_start + (rewind_stack.len() as u64); - if rewind_data_end > env.stack_end { + if rewind_data_end > env.layout.stack_upper { warn!( "attempting to rewind a stack bigger than the allocated stack space ({} > {})", - rewind_data_end, env.stack_end + rewind_data_end, env.layout.stack_upper ); return Errno::Overflow; } let rewind_data = __wasi_asyncify_t:: { start: wasi_try!(rewind_data_end.try_into().map_err(|_| Errno::Overflow)), - end: wasi_try!(env.stack_end.try_into().map_err(|_| Errno::Overflow)), + end: wasi_try!(env + .layout + .stack_upper + .try_into() + .map_err(|_| Errno::Overflow)), }; let rewind_data_ptr: WasmPtr<__wasi_asyncify_t, M> = WasmPtr::new(wasi_try!(rewind_pointer @@ -1015,7 +1264,8 @@ pub(crate) fn handle_rewind(ctx: &mut FunctionEnvMut<'_, WasiEnv> } // Restore the memory stack - set_memory_stack::(ctx, memory_stack); + let (env, mut store) = ctx.data_and_store_mut(); + set_memory_stack::(env, &mut store, memory_stack); true } else { false diff --git a/lib/wasi/src/syscalls/wasi/poll_oneoff.rs b/lib/wasi/src/syscalls/wasi/poll_oneoff.rs index 7920de6f4c9..c3b89384af6 100644 --- a/lib/wasi/src/syscalls/wasi/poll_oneoff.rs +++ b/lib/wasi/src/syscalls/wasi/poll_oneoff.rs @@ -22,8 +22,8 @@ use crate::{ /// Output: /// - `u32 nevents` /// The number of events seen -#[instrument(level = "trace", skip_all, fields(timeout_ns = field::Empty, fd_guards = field::Empty, seen = field::Empty), ret, err)] -pub fn poll_oneoff( +#[instrument(level = "trace", skip_all, fields(timeout_ms = field::Empty, fd_guards = field::Empty, seen = field::Empty), ret, err)] +pub fn poll_oneoff( mut ctx: FunctionEnvMut<'_, WasiEnv>, in_: WasmPtr, out_: WasmPtr, @@ -45,47 +45,49 @@ pub fn poll_oneoff( subscriptions.push((None, PollEventSet::default(), s)); } - // Poll and receive all the events that triggered - let triggered_events = poll_oneoff_internal(&mut ctx, subscriptions)?; - let triggered_events = match triggered_events { - Ok(a) => a, - Err(err) => { - return Ok(err); + // We clear the number of events + wasi_try_mem_ok!(nevents.write(&memory, M::ZERO)); + + // Function to invoke once the poll is finished + let process_events = move |memory: &'_ Memory, store: &'_ dyn AsStoreRef, triggered_events| { + // Process all the events that were triggered + let mut view = memory.view(store); + let mut events_seen: u32 = 0; + let event_array = wasi_try_mem!(out_.slice(&view, nsubscriptions)); + for event in triggered_events { + wasi_try_mem!(event_array.index(events_seen as u64).write(event)); + events_seen += 1; } + let events_seen: M::Offset = wasi_try!(events_seen.try_into().map_err(|_| Errno::Overflow)); + let out_ptr = nevents.deref(&view); + wasi_try_mem!(out_ptr.write(events_seen)); + Errno::Success }; - // Process all the events that were triggered - let mut env = ctx.data(); - let mut memory = env.memory_view(&ctx); - let mut events_seen: u32 = 0; - let event_array = wasi_try_mem_ok!(out_.slice(&memory, nsubscriptions)); - for event in triggered_events { - wasi_try_mem_ok!(event_array.index(events_seen as u64).write(event)); - events_seen += 1; - } - let events_seen: M::Offset = wasi_try_ok!(events_seen.try_into().map_err(|_| Errno::Overflow)); - let out_ptr = nevents.deref(&memory); - wasi_try_mem_ok!(out_ptr.write(events_seen)); - Ok(Errno::Success) + // Poll and receive all the events that triggered + poll_oneoff_internal::(ctx, subscriptions, process_events) } -struct PollBatch<'a> { +struct PollBatch { pid: WasiProcessId, tid: WasiThreadId, evts: Vec, - joins: Vec>, + joins: Vec, } -impl<'a> PollBatch<'a> { - fn new(pid: WasiProcessId, tid: WasiThreadId, fds: &'a mut [InodeValFilePollGuard]) -> Self { +impl PollBatch { + fn new(pid: WasiProcessId, tid: WasiThreadId, fds: Vec) -> Self { Self { pid, tid, evts: Vec::new(), - joins: fds.iter_mut().map(InodeValFilePollGuardJoin::new).collect(), + joins: fds + .into_iter() + .map(InodeValFilePollGuardJoin::new) + .collect(), } } } -impl<'a> Future for PollBatch<'a> { +impl Future for PollBatch { type Output = Result, Errno>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let pid = self.pid; @@ -95,12 +97,19 @@ impl<'a> Future for PollBatch<'a> { let mut evts = Vec::new(); for mut join in self.joins.iter_mut() { let fd = join.fd(); + let peb = join.peb(); let mut guard = Pin::new(join); match guard.poll(cx) { Poll::Pending => {} Poll::Ready(e) => { for evt in e { - tracing::trace!(fd, userdata = evt.userdata, ty = evt.type_ as u8,); + tracing::trace!( + fd, + userdata = evt.userdata, + ty = evt.type_ as u8, + peb, + "triggered" + ); evts.push(evt); } } @@ -127,10 +136,19 @@ impl<'a> Future for PollBatch<'a> { /// Output: /// - `u32 nevents` /// The number of events seen -pub(crate) fn poll_oneoff_internal( - ctx: &mut FunctionEnvMut<'_, WasiEnv>, +pub(crate) fn poll_oneoff_internal( + mut ctx: FunctionEnvMut<'_, WasiEnv>, mut subs: Vec<(Option, PollEventSet, Subscription)>, -) -> Result, Errno>, WasiError> { + process_events: After, +) -> Result +where + After: FnOnce(&'_ Memory, &'_ dyn AsStoreRef, Vec) -> Errno + Send + Sync + 'static, +{ + wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + if handle_rewind::(&mut ctx) { + return Ok(Errno::Success); + } + let pid = ctx.data().pid(); let tid = ctx.data().tid(); @@ -159,10 +177,10 @@ pub(crate) fn poll_oneoff_internal( fd => { let fd_entry = match state.fs.get_fd(fd) { Ok(a) => a, - Err(err) => return Ok(Err(err)), + Err(err) => return Ok(err), }; if !fd_entry.rights.contains(Rights::POLL_FD_READWRITE) { - return Ok(Err(Errno::Access)); + return Ok(Errno::Access); } } } @@ -177,10 +195,10 @@ pub(crate) fn poll_oneoff_internal( fd => { let fd_entry = match state.fs.get_fd(fd) { Ok(a) => a, - Err(err) => return Ok(Err(err)), + Err(err) => return Ok(err), }; if !fd_entry.rights.contains(Rights::POLL_FD_READWRITE) { - return Ok(Err(Errno::Access)); + return Ok(Errno::Access); } } } @@ -214,7 +232,7 @@ pub(crate) fn poll_oneoff_internal( continue; } else { error!("polling not implemented for these clocks yet"); - return Ok(Err(Errno::Inval)); + return Ok(Errno::Inval); } } }; @@ -222,7 +240,7 @@ pub(crate) fn poll_oneoff_internal( let mut events_seen: u32 = 0; - let ret = { + let batch = { // Build the batch of things we are going to poll let state = ctx.data().state.clone(); let tasks = ctx.data().tasks().clone(); @@ -236,19 +254,19 @@ pub(crate) fn poll_oneoff_internal( if let Some(fd) = fd { let wasi_file_ref = match fd { __WASI_STDERR_FILENO => { - wasi_try_ok_ok!(WasiInodes::stderr(&state.fs.fd_map) + wasi_try_ok!(WasiInodes::stderr(&state.fs.fd_map) .map(|g| g.into_poll_guard(fd, peb, s)) .map_err(fs_error_into_wasi_err)) } __WASI_STDOUT_FILENO => { - wasi_try_ok_ok!(WasiInodes::stdout(&state.fs.fd_map) + wasi_try_ok!(WasiInodes::stdout(&state.fs.fd_map) .map(|g| g.into_poll_guard(fd, peb, s)) .map_err(fs_error_into_wasi_err)) } _ => { - let fd_entry = wasi_try_ok_ok!(state.fs.get_fd(fd)); + let fd_entry = wasi_try_ok!(state.fs.get_fd(fd)); if !fd_entry.rights.contains(Rights::POLL_FD_READWRITE) { - return Ok(Err(Errno::Access)); + return Ok(Errno::Access); } let inode = fd_entry.inode; @@ -259,7 +277,7 @@ pub(crate) fn poll_oneoff_internal( { guard } else { - return Ok(Err(Errno::Badf)); + return Ok(Errno::Badf); } } } @@ -278,64 +296,78 @@ pub(crate) fn poll_oneoff_internal( fd_guards }; - // If the time is infinite then we omit the time_to_sleep parameter - let asyncify_time = match time_to_sleep { - Duration::ZERO => { - Span::current().record("timeout_ns", "nonblocking"); - Some(Duration::ZERO) - } - Duration::MAX => { - Span::current().record("timeout_ns", "infinite"); - None - } - time => { - Span::current().record("timeout_ns", time.as_millis()); - Some(time) - } - }; - // Block polling the file descriptors - let batch = PollBatch::new(pid, tid, &mut guards); - __asyncify(ctx, asyncify_time, batch)? + PollBatch::new(pid, tid, guards) }; - let mut env = ctx.data(); - memory = env.memory_view(&ctx); - - // Process the result - match ret { - Ok(evts) => { - // If its a timeout then return an event for it - Span::current().record("seen", evts.len()); - Ok(Ok(evts)) + // If the time is infinite then we omit the time_to_sleep parameter + let asyncify_time = match time_to_sleep { + Duration::ZERO => { + Span::current().record("timeout_ns", "nonblocking"); + Some(Duration::ZERO) } - Err(Errno::Timedout) => { - // The timeout has triggerred so lets add that event - if clock_subs.is_empty() { - tracing::warn!("triggered_timeout (without any clock subscriptions)",); - } - let mut evts = Vec::new(); - for (clock_info, userdata) in clock_subs { - let evt = Event { - userdata, - error: Errno::Success, - type_: Eventtype::Clock, - u: EventUnion { clock: 0 }, - }; - Span::current().record( - "seen", - &format!( - "clock(id={},userdata={})", - clock_info.clock_id as u32, evt.userdata - ), - ); - evts.push(evt); - } - Ok(Ok(evts)) + Duration::MAX => { + Span::current().record("timeout_ns", "infinite"); + None } - // If nonblocking the Errno::Again needs to be turned into an empty list - Err(Errno::Again) => Ok(Ok(Default::default())), - // Otherwise process the rror - Err(err) => Ok(Err(err)), - } + time => { + Span::current().record("timeout_ns", time.as_millis()); + Some(time) + } + }; + + // We use asyncify with a deep sleep to wait on new IO events + let res = __asyncify_with_deep_sleep_ext::( + ctx, + asyncify_time, + Duration::from_millis(50), + batch, + move |memory, store, res| { + let events = res.unwrap_or_else(Err); + // Process the result + match events { + Ok(evts) => { + // If its a timeout then return an event for it + Span::current().record("seen", evts.len()); + + // Process the events + process_events(memory, store, evts); + } + Err(Errno::Timedout) => { + // The timeout has triggerred so lets add that event + if clock_subs.is_empty() { + tracing::warn!("triggered_timeout (without any clock subscriptions)",); + } + let mut evts = Vec::new(); + for (clock_info, userdata) in clock_subs { + let evt = Event { + userdata, + error: Errno::Success, + type_: Eventtype::Clock, + u: EventUnion { clock: 0 }, + }; + Span::current().record( + "seen", + &format!( + "clock(id={},userdata={})", + clock_info.clock_id as u32, evt.userdata + ), + ); + evts.push(evt); + } + process_events(memory, store, evts); + } + // If nonblocking the Errno::Again needs to be turned into an empty list + Err(Errno::Again) => { + process_events(memory, store, Default::default()); + } + // Otherwise process the error + Err(err) => { + tracing::warn!("failed to poll during deep sleep - {}", err); + } + } + Ok(()) + }, + )?; + Ok(Errno::Success) } diff --git a/lib/wasi/src/syscalls/wasi/proc_exit.rs b/lib/wasi/src/syscalls/wasi/proc_exit.rs index e0a2d885f21..ef3122dc7ad 100644 --- a/lib/wasi/src/syscalls/wasi/proc_exit.rs +++ b/lib/wasi/src/syscalls/wasi/proc_exit.rs @@ -18,11 +18,14 @@ pub fn proc_exit( // If we are in a vfork we need to return to the point we left off if let Some(mut vfork) = ctx.data_mut().vfork.take() { // Restore the WasiEnv to the point when we vforked - std::mem::swap(&mut vfork.env.inner, &mut ctx.data_mut().inner); + vfork.env.swap_inner(ctx.data_mut()); std::mem::swap(vfork.env.as_mut(), ctx.data_mut()); let mut wasi_env = *vfork.env; wasi_env.owned_handles.push(vfork.handle); + // The child environment needs to be notified as exited + wasi_env.thread.set_status_finished(Ok(code)); + // We still need to create the process that exited so that // the exit code can be used by the parent process let pid = wasi_env.process.pid(); @@ -32,11 +35,14 @@ pub fn proc_exit( // If the return value offset is within the memory stack then we need // to update it here rather than in the real memory + let val_bytes = pid.raw().to_ne_bytes(); let pid_offset: u64 = vfork.pid_offset; - if pid_offset >= wasi_env.stack_start && pid_offset < wasi_env.stack_end { + if pid_offset >= wasi_env.layout.stack_lower + && (pid_offset + val_bytes.len() as u64) <= wasi_env.layout.stack_upper + { // Make sure its within the "active" part of the memory stack - let offset = wasi_env.stack_end - pid_offset; - if offset as usize > memory_stack.len() { + let offset = wasi_env.layout().stack_upper - pid_offset; + if (offset as usize + val_bytes.len()) > memory_stack.len() { warn!( "fork failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", offset, @@ -46,7 +52,6 @@ pub fn proc_exit( } // Update the memory stack with the new PID - let val_bytes = pid.raw().to_ne_bytes(); let pstart = memory_stack.len() - offset as usize; let pend = pstart + val_bytes.len(); let pbytes = &mut memory_stack[pstart..pend]; diff --git a/lib/wasi/src/syscalls/wasi/thread_spawn.rs b/lib/wasi/src/syscalls/wasi/thread_spawn.rs index 0d9a6a9962a..cbeb5d23df3 100644 --- a/lib/wasi/src/syscalls/wasi/thread_spawn.rs +++ b/lib/wasi/src/syscalls/wasi/thread_spawn.rs @@ -21,7 +21,7 @@ pub fn thread_spawn_legacy( mut ctx: FunctionEnvMut<'_, WasiEnv>, start_ptr: WasmPtr, M>, ) -> i32 { - thread_spawn_internal(&ctx, start_ptr) + thread_spawn_internal(&mut ctx, start_ptr) .map(|tid| tid as i32) .map_err(|errno| errno as i32) .unwrap_or_else(|err| -err) diff --git a/lib/wasi/src/syscalls/wasix/callback_signal.rs b/lib/wasi/src/syscalls/wasix/callback_signal.rs index f18ea8b563b..5dc6e3c644b 100644 --- a/lib/wasi/src/syscalls/wasix/callback_signal.rs +++ b/lib/wasi/src/syscalls/wasix/callback_signal.rs @@ -38,7 +38,7 @@ pub fn callback_signal( Span::current().record("funct_is_some", funct.is_some()); { - let inner = ctx.data_mut().inner_mut(); + let mut inner = ctx.data_mut().inner_mut(); inner.signal = funct; inner.signal_set = true; } diff --git a/lib/wasi/src/syscalls/wasix/futex_wait.rs b/lib/wasi/src/syscalls/wasix/futex_wait.rs index 50ff44e5c0a..570465f3f33 100644 --- a/lib/wasi/src/syscalls/wasix/futex_wait.rs +++ b/lib/wasi/src/syscalls/wasix/futex_wait.rs @@ -3,57 +3,89 @@ use std::task::Waker; use super::*; use crate::syscalls::*; -struct FutexPoller<'a, M> -where - M: MemorySize, -{ - env: &'a WasiEnv, - view: MemoryView<'a>, +#[derive(Clone)] +struct FutexPoller { + state: Arc, + poller_idx: u64, futex_idx: u64, - futex_ptr: WasmPtr, expected: u32, } -impl<'a, M> Future for FutexPoller<'a, M> -where - M: MemorySize, -{ +impl Future for FutexPoller { type Output = Result<(), Errno>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let waker = cx.waker(); - let mut guard = self.env.state.futexs.lock().unwrap(); - - { - let val = match self.futex_ptr.read(&self.view) { - Ok(a) => a, - Err(err) => return Poll::Ready(Err(mem_error_to_wasi(err))), - }; - if val != self.expected { - return Poll::Ready(Ok(())); - } - } + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut guard = self.state.futexs.lock().unwrap(); - let futex = guard - .entry(self.futex_idx) - .or_insert_with(|| WasiFutex { wakers: vec![] }); - if !futex.wakers.iter().any(|w| w.will_wake(waker)) { - futex.wakers.push(waker.clone()); - } + // If the futex itself is no longer registered then it was likely + // woken by a wake call + let futex = match guard.futexes.get_mut(&self.futex_idx) { + Some(f) => f, + None => return Poll::Ready(Ok(())), + }; + let waker = match futex.wakers.get_mut(&self.poller_idx) { + Some(w) => w, + None => return Poll::Ready(Ok(())), + }; + + // Register the waker + waker.replace(cx.waker().clone()); + // We will now wait to be woken Poll::Pending } } -impl<'a, M> Drop for FutexPoller<'a, M> +impl Drop for FutexPoller { + fn drop(&mut self) { + let mut guard = self.state.futexs.lock().unwrap(); + + let mut should_remove = false; + if let Some(futex) = guard.futexes.get_mut(&self.futex_idx) { + if let Some(Some(waker)) = futex.wakers.remove(&self.poller_idx) { + waker.wake(); + } + should_remove = futex.wakers.is_empty(); + } + if should_remove { + guard.futexes.remove(&self.futex_idx); + } + } +} + +/// The futex after struct will write the response of whether the +/// futex was actually woken or not to the return memory of the syscall +/// callee after the wake event has been triggered. +/// +/// It is encased in this struct so that it can be passed around +/// between threads and execute after the threads are rewound (in an +/// asynchronous threading situation). +/// +/// The same implementation is used for both synchronous and +/// asynchronous threading. +/// +/// It is not possible to include this logic directly in the poller +/// as the poller runs before the stack is rewound and the memory +/// that this writes to is often a pointer to the stack hence a +/// rewind would override whatever is written. +struct FutexAfter +where + M: MemorySize, +{ + ret_woken: WasmPtr, +} +impl RewindPostProcess for FutexAfter where M: MemorySize, { - fn drop(&mut self) { - let futex = { - let mut guard = self.env.state.futexs.lock().unwrap(); - guard.remove(&self.futex_idx) - }; - if let Some(futex) = futex { - futex.wakers.into_iter().for_each(|w| w.wake()); - } + fn finish( + &mut self, + env: &WasiEnv, + store: &dyn AsStoreRef, + res: Result<(), Errno>, + ) -> Result<(), ExitCode> { + let view = env.memory_view(store); + self.ret_woken + .write(&view, Bool::True) + .map_err(mem_error_to_wasi) + .map_err(ExitCode::Errno) } } @@ -66,8 +98,8 @@ where /// * `futex` - Memory location that holds the value that will be checked /// * `expected` - Expected value that should be currently held at the memory location /// * `timeout` - Timeout should the futex not be triggered in the allocated time -#[instrument(level = "trace", skip_all, fields(?futex_ptr, futex_idx = field::Empty, %expected, timeout = field::Empty, woken = field::Empty), err)] -pub fn futex_wait( +#[instrument(level = "trace", skip_all, fields(futex_idx = field::Empty, poller_idx = field::Empty, %expected, timeout = field::Empty, woken = field::Empty), err)] +pub fn futex_wait( mut ctx: FunctionEnvMut<'_, WasiEnv>, futex_ptr: WasmPtr, expected: u32, @@ -76,6 +108,16 @@ pub fn futex_wait( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + // If we were just restored then we were woken after a deep sleep + // and thus we repeat all the checks again, we do not immediately + // exit here as it could be the case that we were woken but the + // expected value does not match + if handle_rewind::(&mut ctx) { + // fall through so the normal checks kick in, this will + // ensure that the expected value has changed before + // this syscall returns even if it was woken + } + // Determine the timeout let mut env = ctx.data(); let timeout = { @@ -92,34 +134,54 @@ pub fn futex_wait( let futex_idx: u64 = wasi_try_ok!(futex_ptr.offset().try_into().map_err(|_| Errno::Overflow)); Span::current().record("futex_idx", futex_idx); - // Create a poller which will register ourselves against - // this futex event and check when it has changed - let view = env.memory_view(&ctx); - let poller = FutexPoller { - env, - view, - futex_idx, - futex_ptr, - expected, - }; + // We generate a new poller which also registers in the + // shared state futex lookup. When this object is dropped + // it will remove itself from the lookup. It can also be + // removed whenever the wake call is invoked (which could + // be before the poller is polled). + let poller = { + let mut guard = env.state.futexs.lock().unwrap(); + guard.poller_seed += 1; + let poller_idx = guard.poller_seed; - // Wait for the futex to trigger or a timeout to occur - let res = __asyncify_light(env, timeout, poller)?; + // We insert the futex before we check the condition variable to avoid + // certain race conditions + let futex = guard.futexes.entry(futex_idx).or_default(); + futex.wakers.insert(poller_idx, Default::default()); - // Process it and return the result - let mut ret = Errno::Success; - let woken = match res { - Err(Errno::Timedout) => Bool::False, - Err(err) => { - ret = err; - Bool::True + Span::current().record("poller_idx", poller_idx); + FutexPoller { + state: env.state.clone(), + poller_idx, + futex_idx, + expected, } - Ok(_) => Bool::True, }; - Span::current().record("woken", woken as u8); + // We check if the expected value has changed let memory = env.memory_view(&ctx); - let mut env = ctx.data(); - wasi_try_mem_ok!(ret_woken.write(&memory, woken)); - Ok(ret) + let val = wasi_try_mem_ok!(futex_ptr.read(&memory)); + if val != expected { + // We have been triggered so do not go into a wait + wasi_try_mem_ok!(ret_woken.write(&memory, Bool::True)); + return Ok(Errno::Success); + } + + // We clear the woken flag (so if the poller fails to trigger + // then the value is not set) - the poller will set it to true + wasi_try_mem_ok!(ret_woken.write(&memory, Bool::False)); + + // Create a poller which will register ourselves against + // this futex event and check when it has changed + let after = FutexAfter { ret_woken }; + + // We use asyncify on the poller and potentially go into deep sleep + __asyncify_with_deep_sleep::( + ctx, + timeout, + Duration::from_millis(50), + Box::pin(poller), + after, + )?; + Ok(Errno::Success) } diff --git a/lib/wasi/src/syscalls/wasix/futex_wake.rs b/lib/wasi/src/syscalls/wasix/futex_wake.rs index e73b24ff3b1..63d754ac796 100644 --- a/lib/wasi/src/syscalls/wasix/futex_wake.rs +++ b/lib/wasi/src/syscalls/wasix/futex_wake.rs @@ -24,12 +24,15 @@ pub fn futex_wake( let mut woken = false; let woken = { let mut guard = state.futexs.lock().unwrap(); - if let Some(futex) = guard.get_mut(&pointer) { - if let Some(w) = futex.wakers.pop() { - w.wake() + if let Some(futex) = guard.futexes.get_mut(&pointer) { + let first = futex.wakers.keys().copied().next(); + if let Some(id) = first { + if let Some(Some(w)) = futex.wakers.remove(&id) { + w.wake(); + } } if futex.wakers.is_empty() { - guard.remove(&pointer); + guard.futexes.remove(&pointer); } true } else { diff --git a/lib/wasi/src/syscalls/wasix/futex_wake_all.rs b/lib/wasi/src/syscalls/wasix/futex_wake_all.rs index 3adc50ea9ef..ad943558868 100644 --- a/lib/wasi/src/syscalls/wasix/futex_wake_all.rs +++ b/lib/wasi/src/syscalls/wasix/futex_wake_all.rs @@ -17,25 +17,28 @@ pub fn futex_wake_all( let state = env.state.deref(); let pointer: u64 = wasi_try!(futex_ptr.offset().try_into().map_err(|_| Errno::Overflow)); - Span::current().record("futex_idx", pointer); + //Span::current().record("futex_idx", pointer); let mut woken = false; let woken = { let mut guard = state.futexs.lock().unwrap(); - if let Some(futex) = guard.remove(&pointer) { - futex.wakers.into_iter().for_each(|w| w.wake()); + if let Some(futex) = guard.futexes.remove(&pointer) { + for waker in futex.wakers { + if let Some(waker) = waker.1 { + waker.wake(); + } + } true } else { true } }; - Span::current().record("woken", woken); + //Span::current().record("woken", woken); let woken = match woken { false => Bool::False, true => Bool::True, }; wasi_try_mem!(ret_woken.write(&memory, woken)); - Errno::Success } diff --git a/lib/wasi/src/syscalls/wasix/proc_exec.rs b/lib/wasi/src/syscalls/wasix/proc_exec.rs index 218ccb3a860..4824480ea84 100644 --- a/lib/wasi/src/syscalls/wasix/proc_exec.rs +++ b/lib/wasi/src/syscalls/wasix/proc_exec.rs @@ -15,7 +15,7 @@ use crate::{ /// ## Return /// /// Returns a bus process id that can be used to invoke calls -#[instrument(level = "debug", skip_all, fields(name = field::Empty, %args_len), ret, err)] +#[instrument(level = "debug", skip_all, fields(name = field::Empty, %args_len), ret)] pub fn proc_exec( mut ctx: FunctionEnvMut<'_, WasiEnv>, name: WasmPtr, @@ -23,6 +23,15 @@ pub fn proc_exec( args: WasmPtr, args_len: M::Offset, ) -> Result<(), WasiError> { + WasiEnv::process_signals_and_exit(&mut ctx)?; + + // If we were just restored the stack then we were woken after a deep sleep + if handle_rewind::(&mut ctx) { + // We should never get here as the process will be termined + // in the `WasiEnv::process_signals_and_exit()` call + return Err(WasiError::Exit(Errno::Unknown.into())); + } + let memory = ctx.data().memory_view(&ctx); let mut name = name.read_utf8_string(&memory, name_len).map_err(|err| { warn!("failed to execve as the name could not be read - {}", err); @@ -71,15 +80,15 @@ pub fn proc_exec( let child_finished = child_process.finished; // Restore the WasiEnv to the point when we vforked - std::mem::swap(&mut vfork.env.inner, &mut ctx.data_mut().inner); + vfork.env.swap_inner(ctx.data_mut()); std::mem::swap(vfork.env.as_mut(), ctx.data_mut()); let mut wasi_env = *vfork.env; wasi_env.owned_handles.push(vfork.handle); _prepare_wasi(&mut wasi_env, Some(args)); // Recrod the stack offsets before we give up ownership of the wasi_env - let stack_base = wasi_env.stack_end; - let stack_start = wasi_env.stack_start; + let stack_lower = wasi_env.layout.stack_lower; + let stack_upper = wasi_env.layout.stack_upper; // Spawn a new process with this current execution environment let mut err_exit_code: ExitCode = Errno::Success.into(); @@ -139,11 +148,12 @@ pub fn proc_exec( // If the return value offset is within the memory stack then we need // to update it here rather than in the real memory + let val_bytes = child_pid.raw().to_ne_bytes(); let pid_offset: u64 = vfork.pid_offset; - if pid_offset >= stack_start && pid_offset < stack_base { + if pid_offset >= stack_lower && (pid_offset + val_bytes.len() as u64) <= stack_upper { // Make sure its within the "active" part of the memory stack - let offset = stack_base - pid_offset; - if offset as usize > memory_stack.len() { + let offset = stack_upper - pid_offset; + if (offset as usize + val_bytes.len()) > memory_stack.len() { warn!( "vfork failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", offset, @@ -151,7 +161,6 @@ pub fn proc_exec( ); } else { // Update the memory stack with the new PID - let val_bytes = child_pid.raw().to_ne_bytes(); let pstart = memory_stack.len() - offset as usize; let pend = pstart + val_bytes.len(); let pbytes = &mut memory_stack[pstart..pend]; @@ -179,87 +188,98 @@ pub fn proc_exec( } } })?; - return Ok(()); + Ok(()) } // Otherwise we need to unwind the stack to get out of the current executing // callstack, steal the memory/WasiEnv and switch it over to a new thread // on the new module else { - // We need to unwind out of this process and launch a new process in its place - unwind::(ctx, move |mut ctx, _, _| { - // Prepare the environment - let mut wasi_env = ctx.data_mut().duplicate(); - _prepare_wasi(&mut wasi_env, Some(args)); + // Prepare the environment + let mut wasi_env = ctx.data().clone(); + _prepare_wasi(&mut wasi_env, Some(args)); - // Get a reference to the runtime - let bin_factory = ctx.data().bin_factory.clone(); - let tasks = wasi_env.tasks().clone(); + // Get a reference to the runtime + let bin_factory = ctx.data().bin_factory.clone(); + let tasks = wasi_env.tasks().clone(); - // Create the process and drop the context - let bin_factory = Box::new(ctx.data().bin_factory.clone()); + // Create the process and drop the context + let bin_factory = Box::new(ctx.data().bin_factory.clone()); - let mut new_store = Some(new_store); - let mut builder = Some(wasi_env); + let mut new_store = Some(new_store); + let mut builder = Some(wasi_env); - let process = match bin_factory.try_built_in( - name.clone(), - Some(&ctx), - &mut new_store, - &mut builder, - ) { - Ok(a) => Ok(Ok(a)), - Err(err) => { - if err != VirtualBusError::NotFound { - error!("builtin failed - {}", err); - } + let process = match bin_factory.try_built_in( + name.clone(), + Some(&ctx), + &mut new_store, + &mut builder, + ) { + Ok(a) => Ok(Ok(a)), + Err(err) => { + if err != VirtualBusError::NotFound { + error!("builtin failed - {}", err); + } - let new_store = new_store.take().unwrap(); - let env = builder.take().unwrap(); + let new_store = new_store.take().unwrap(); + let env = builder.take().unwrap(); - // Spawn a new process with this current execution environment - //let pid = wasi_env.process.pid(); - let (tx, rx) = std::sync::mpsc::channel(); - tasks.block_on(Box::pin(async move { - let ret = bin_factory.spawn(name, new_store, env).await; - tx.send(ret); - })); - rx.recv() - } - }; + // Spawn a new process with this current execution environment + //let pid = wasi_env.process.pid(); + let (tx, rx) = std::sync::mpsc::channel(); + tasks.block_on(Box::pin(async move { + let ret = bin_factory.spawn(name, new_store, env).await; + tx.send(ret); + })); + rx.recv() + } + }; + + match process { + Ok(Ok(mut process)) => { + // If we support deep sleeping then we switch to deep sleep mode + let env = ctx.data(); + let thread = env.thread.clone(); - match process { - Ok(Ok(mut process)) => { - // Wait for the sub-process to exit itself - then we will exit - let (tx, rx) = std::sync::mpsc::channel(); - let tasks_inner = tasks.clone(); - tasks.block_on(Box::pin(async move { - let code = process + // The poller will wait for the process to actually finish + let res = __asyncify_with_deep_sleep_ext::( + ctx, + None, + Duration::from_millis(50), + async move { + process .wait_finished() .await - .unwrap_or_else(|_| Errno::Child.into()); - tx.send(code); - })); - let exit_code = rx.recv().unwrap(); - OnCalledAction::Trap(Box::new(WasiError::Exit(exit_code))) - } - Ok(Err(err)) => { - warn!( - "failed to execve as the process could not be spawned (fork)[0] - {}", - err - ); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Noexec.into()))) - } - Err(err) => { - warn!( - "failed to execve as the process could not be spawned (fork)[1] - {}", - err - ); - OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Noexec.into()))) + .unwrap_or_else(|_| Errno::Child.into()) + }, + move |_, _, res| { + let exit_code = res.unwrap_or_else(ExitCode::Errno); + thread.set_status_finished(Ok(exit_code)); + Ok(()) + }, + )?; + match res { + AsyncifyAction::Finish(mut ctx) => { + // When we arrive here the process should already be terminated + WasiEnv::process_signals_and_exit(&mut ctx)?; + Err(WasiError::Exit(Errno::Unknown.into())) + } + AsyncifyAction::Unwind => Ok(()), } } - })?; + Ok(Err(err)) => { + warn!( + "failed to execve as the process could not be spawned (fork)[0] - {}", + err + ); + Err(WasiError::Exit(Errno::Noexec.into())) + } + Err(err) => { + warn!( + "failed to execve as the process could not be spawned (fork)[1] - {}", + err + ); + Err(WasiError::Exit(Errno::Noexec.into())) + } + } } - - // Success - Ok(()) } diff --git a/lib/wasi/src/syscalls/wasix/proc_fork.rs b/lib/wasi/src/syscalls/wasix/proc_fork.rs index 5f97fc837c8..35c1e3f0091 100644 --- a/lib/wasi/src/syscalls/wasix/proc_fork.rs +++ b/lib/wasi/src/syscalls/wasix/proc_fork.rs @@ -1,5 +1,11 @@ use super::*; -use crate::{os::task::OwnedTaskStatus, syscalls::*}; +use crate::{ + capture_snapshot, + os::task::OwnedTaskStatus, + runtime::task_manager::{TaskWasm, TaskWasmRunProperties}, + syscalls::*, + WasiThreadHandle, +}; use wasmer::Memory; /// ### `proc_fork()` @@ -50,7 +56,11 @@ pub fn proc_fork( } let env = ctx.data(); let memory = env.memory_view(&ctx); + + // Setup some properties in the child environment wasi_try_mem_ok!(pid_ptr.write(&memory, 0)); + let pid = child_env.pid(); + let tid = child_env.tid(); // Pass some offsets to the unwind function let pid_offset = pid_ptr.offset(); @@ -72,7 +82,7 @@ pub fn proc_fork( // We first fork the environment and replace the current environment // so that the process can continue to prepare for the real fork as // if it had actually forked - std::mem::swap(&mut ctx.data_mut().inner, &mut child_env.inner); + child_env.swap_inner(ctx.data_mut()); std::mem::swap(ctx.data_mut(), &mut child_env); ctx.data_mut().vfork.replace(WasiVFork { rewind_stack: rewind_stack.clone(), @@ -106,6 +116,7 @@ pub fn proc_fork( let bin_factory = env.bin_factory.clone(); // Perform the unwind action + let snapshot = capture_snapshot(&mut ctx.as_store_mut()); unwind::(ctx, move |mut ctx, mut memory_stack, rewind_stack| { let span = debug_span!( "unwind", @@ -115,78 +126,36 @@ pub fn proc_fork( let _span_guard = span.enter(); // Grab all the globals and serialize them - let store_data = crate::utils::store::capture_snapshot(&mut ctx.as_store_mut()) - .serialize() - .unwrap(); + let store_data = snapshot.serialize().unwrap(); let store_data = Bytes::from(store_data); - let mut fork_store = ctx.data().runtime.new_store(); - - // Fork the memory and copy the module (compiled code) - let (env, mut store) = ctx.data_and_store_mut(); - let fork_memory: Memory = match env.memory().duplicate_in_store(&store, &mut fork_store) { - Some(memory) => memory, - None => { - warn!("Can't duplicate memory in new store"); - return OnCalledAction::Trap(Box::new(WasiError::Exit(Errno::Memviolation.into()))); - } - }; - let fork_module = env.inner().instance.module().clone(); - // Now we use the environment and memory references let runtime = child_env.runtime.clone(); let tasks = child_env.tasks().clone(); let child_memory_stack = memory_stack.clone(); let child_rewind_stack = rewind_stack.clone(); + let module = ctx.data().inner().module_clone(); + let memory = ctx.data().memory_clone(); + let spawn_type = SpawnMemoryType::CopyMemory(memory, ctx.as_store_ref()); + // Spawn a new process with this current execution environment let signaler = Box::new(child_env.process.clone()); { - let store_data = store_data.clone(); let runtime = runtime.clone(); let tasks = tasks.clone(); let tasks_outer = tasks.clone(); + let store_data = store_data.clone(); - let store = fork_store; - let module = fork_module; - - let spawn_type = SpawnType::NewThread(fork_memory); - let task = move |mut store, module, memory: Option| { - // Create the WasiFunctionEnv - let pid = child_env.pid(); - let tid = child_env.tid(); - child_env.runtime = runtime.clone(); - let mut ctx = WasiFunctionEnv::new(&mut store, child_env); - // fork_store, fork_module, - - // Let's instantiate the module with the imports. - let (mut import_object, init) = - import_object_for_all_wasi_versions(&module, &mut store, &ctx.env); - let memory = if let Some(memory) = memory { - import_object.define("env", "memory", memory.clone()); - memory - } else { - error!("wasm instantiate failed - no memory supplied",); - return; - }; - let instance = match Instance::new(&mut store, &module, &import_object) { - Ok(a) => a, - Err(err) => { - error!("wasm instantiate error ({})", err); - return; - } - }; - - init(&instance, &store).unwrap(); - - // Set the current thread ID - ctx.data_mut(&mut store).inner = - Some(WasiInstanceHandles::new(memory, &store, instance)); + let run = move |mut props: TaskWasmRunProperties| { + let ctx = props.ctx; + let mut store = props.store; // Rewind the stack and carry on { trace!("rewinding child"); - let ctx = ctx.env.clone().into_mut(&mut store); + let mut ctx = ctx.env.clone().into_mut(&mut store); + let (data, mut store) = ctx.data_and_store_mut(); match rewind::( ctx, child_memory_stack.freeze(), @@ -205,32 +174,15 @@ pub fn proc_fork( } // Invoke the start function - let mut ret: ExitCode = Errno::Success.into(); - let err = if ctx.data(&store).thread.is_main() { - trace!("re-invoking main"); - let start = ctx.data(&store).inner().start.clone().unwrap(); - start.call(&mut store) - } else { - trace!("re-invoking thread_spawn"); - let start = ctx.data(&store).inner().thread_spawn.clone().unwrap(); - start.call(&mut store, 0, 0) - }; - if let Err(err) = err { - if let Ok(WasiError::Exit(exit_code)) = err.downcast::() { - ret = exit_code; - } - } - trace!(%pid, %tid, "child exited (code = {})", ret); - - // Clean up the environment - ctx.cleanup((&mut store), Some(ret)); - - // Send the result - drop(child_handle); + run::(ctx, store, child_handle, None); }; tasks_outer - .task_wasm(Box::new(task), store, module, spawn_type) + .task_wasm( + TaskWasm::new(Box::new(run), child_env, module, false) + .with_snapshot(&snapshot) + .with_memory(spawn_type), + ) .map_err(|err| { warn!( "failed to fork as the process could not be spawned - {}", @@ -243,10 +195,14 @@ pub fn proc_fork( // If the return value offset is within the memory stack then we need // to update it here rather than in the real memory + let env = ctx.data(); + let val_bytes = child_pid.raw().to_ne_bytes(); let pid_offset: u64 = pid_offset.into(); - if pid_offset >= env.stack_start && pid_offset < env.stack_end { + if pid_offset >= env.layout.stack_lower + && (pid_offset + val_bytes.len() as u64) <= env.layout.stack_upper + { // Make sure its within the "active" part of the memory stack - let offset = env.stack_end - pid_offset; + let offset = env.layout.stack_upper - pid_offset; if offset as usize > memory_stack.len() { warn!( "failed - the return value (pid) is outside of the active part of the memory stack ({} vs {})", @@ -257,7 +213,6 @@ pub fn proc_fork( } // Update the memory stack with the new PID - let val_bytes = child_pid.raw().to_ne_bytes(); let pstart = memory_stack.len() - offset as usize; let pend = pstart + val_bytes.len(); let pbytes = &mut memory_stack[pstart..pend]; @@ -284,3 +239,74 @@ pub fn proc_fork( } }) } + +fn run( + ctx: WasiFunctionEnv, + mut store: Store, + child_handle: WasiThreadHandle, + rewind_state: Option<(RewindState, Result<(), Errno>)>, +) -> ExitCode { + let env = ctx.data(&store); + let tasks = env.tasks().clone(); + let pid = env.pid(); + let tid = env.tid(); + + // If we need to rewind then do so + if let Some((mut rewind_state, trigger_res)) = rewind_state { + if let Err(exit_code) = rewind_state.rewinding_finish::(&ctx, &mut store, trigger_res) { + return exit_code; + } + let res = rewind::( + ctx.env.clone().into_mut(&mut store), + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + ); + if res != Errno::Success { + return res.into(); + } + } + + let mut ret: ExitCode = Errno::Success.into(); + let err = if ctx.data(&store).thread.is_main() { + trace!(%pid, %tid, "re-invoking main"); + let start = ctx.data(&store).inner().start.clone().unwrap(); + start.call(&mut store) + } else { + trace!(%pid, %tid, "re-invoking thread_spawn"); + let start = ctx.data(&store).inner().thread_spawn.clone().unwrap(); + start.call(&mut store, 0, 0) + }; + if let Err(err) = err { + match err.downcast::() { + Ok(WasiError::Exit(exit_code)) => { + ret = exit_code; + } + Ok(WasiError::DeepSleep(deep)) => { + trace!(%pid, %tid, "entered a deep sleep"); + + // Create the respawn function + let respawn = { + let tasks = tasks.clone(); + let rewind_state = deep.rewind; + move |ctx, store, trigger_res| { + run::(ctx, store, child_handle, Some((rewind_state, trigger_res))); + } + }; + + /// Spawns the WASM process after a trigger + tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger); + return Errno::Success.into(); + } + _ => {} + } + } + trace!(%pid, %tid, "child exited (code = {})", ret); + + // Clean up the environment and return the result + ctx.cleanup((&mut store), Some(ret)); + + // We drop the handle at the last moment which will close the thread + drop(child_handle); + ret +} diff --git a/lib/wasi/src/syscalls/wasix/proc_join.rs b/lib/wasi/src/syscalls/wasix/proc_join.rs index 7e551cc00c7..856f4ffecea 100644 --- a/lib/wasi/src/syscalls/wasix/proc_join.rs +++ b/lib/wasi/src/syscalls/wasix/proc_join.rs @@ -1,7 +1,7 @@ use wasmer_wasix_types::wasi::{JoinFlags, JoinStatus, JoinStatusType, JoinStatusUnion, OptionPid}; use super::*; -use crate::syscalls::*; +use crate::{syscalls::*, WasiProcess}; /// ### `proc_join()` /// Joins the child process, blocking this one until the other finishes @@ -10,7 +10,7 @@ use crate::syscalls::*; /// /// * `pid` - Handle of the child process to wait on #[instrument(level = "trace", skip_all, fields(pid = ctx.data().process.pid().raw()), ret, err)] -pub fn proc_join( +pub fn proc_join( mut ctx: FunctionEnvMut<'_, WasiEnv>, pid_ptr: WasmPtr, _flags: JoinFlags, @@ -18,6 +18,39 @@ pub fn proc_join( ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + // This lambda will look at what we wrote in the status variable + // and use this to determine the return code sent back to the caller + let ret_result = { + let status_ptr = status_ptr; + move |ctx: FunctionEnvMut<'_, WasiEnv>| { + let view = ctx.data().memory_view(&ctx); + let status = wasi_try_mem_ok!(status_ptr.read(&view)); + if status.tag == JoinStatusType::Nothing { + let ret = unsafe { status.u.nothing_errno }; + wasi_try_mem_ok!(status_ptr.write( + &view, + JoinStatus { + tag: JoinStatusType::Nothing, + u: JoinStatusUnion { + nothing_errno: Errno::Success + }, + } + )); + Ok(ret) + } else { + Ok(Errno::Success) + } + } + }; + + // If we were just restored the stack then we were woken after a deep sleep + // and the return calues are already set + if handle_rewind::(&mut ctx) { + let ret = ret_result(ctx); + tracing::trace!("rewound join ret={:?}", ret); + return ret; + } + let env = ctx.data(); let memory = env.memory_view(&ctx); let option_pid = wasi_try_mem_ok!(pid_ptr.read(&memory)); @@ -27,58 +60,101 @@ pub fn proc_join( }; tracing::trace!("filter_pid = {:?}", option_pid); + // Clear the existing values (in case something goes wrong) + wasi_try_mem_ok!(pid_ptr.write( + &memory, + OptionPid { + tag: OptionTag::None, + pid: 0, + } + )); + wasi_try_mem_ok!(status_ptr.write( + &memory, + JoinStatus { + tag: JoinStatusType::Nothing, + u: JoinStatusUnion { + nothing_errno: Errno::Success + }, + } + )); + // If the ID is maximum then it means wait for any of the children let pid = match option_pid { None => { let mut process = ctx.data_mut().process.clone(); - let child_exit = wasi_try_ok!(__asyncify(&mut ctx, None, async move { - process.join_any_child().await - })?); - return match child_exit { - Some((pid, exit_code)) => { - trace!(ret_id = pid.raw(), exit_code = exit_code.raw()); - let env = ctx.data(); - let memory = env.memory_view(&ctx); - - let option_pid = OptionPid { - tag: OptionTag::Some, - pid: pid.raw() as Pid, - }; - wasi_try_mem_ok!(pid_ptr.write(&memory, option_pid)); - - let status = JoinStatus { - tag: JoinStatusType::ExitNormal, - u: JoinStatusUnion { - exit_normal: exit_code.into(), - }, - }; - wasi_try_mem_ok!(status_ptr.write(&memory, status)); - Ok(Errno::Success) - } - None => { - let env = ctx.data(); - let memory = env.memory_view(&ctx); - - let status = JoinStatus { - tag: JoinStatusType::Nothing, - u: JoinStatusUnion { nothing: 0 }, - }; - wasi_try_mem_ok!(status_ptr.write(&memory, status)); - Ok(Errno::Child) - } + let pid_ptr = pid_ptr; + let status_ptr = status_ptr; + + // We wait for any process to exit (if it takes too long + // then we go into a deep sleep) + let res = __asyncify_with_deep_sleep_ext::( + ctx, + None, + Duration::from_millis(50), + async move { process.join_any_child().await }, + move |memory, store, res| { + let child_exit = res.unwrap_or_else(Err); + + let memory = memory.view(store); + match child_exit { + Ok(Some((pid, exit_code))) => { + trace!(ret_id = pid.raw(), exit_code = exit_code.raw()); + + let option_pid = OptionPid { + tag: OptionTag::Some, + pid: pid.raw() as Pid, + }; + pid_ptr.write(&memory, option_pid).ok(); + + let status = JoinStatus { + tag: JoinStatusType::ExitNormal, + u: JoinStatusUnion { + exit_normal: exit_code.into(), + }, + }; + status_ptr + .write(&memory, status) + .map_err(mem_error_to_wasi)?; + } + Ok(None) => { + let status = JoinStatus { + tag: JoinStatusType::Nothing, + u: JoinStatusUnion { + nothing_errno: Errno::Child, + }, + }; + status_ptr + .write(&memory, status) + .map_err(mem_error_to_wasi)?; + } + Err(err) => { + let status = JoinStatus { + tag: JoinStatusType::Nothing, + u: JoinStatusUnion { nothing_errno: err }, + }; + status_ptr + .write(&memory, status) + .map_err(mem_error_to_wasi)?; + } + } + Ok(()) + }, + )?; + return match res { + AsyncifyAction::Finish(ctx) => ret_result(ctx), + AsyncifyAction::Unwind => Ok(Errno::Success), }; } Some(pid) => pid, }; // Otherwise we wait for the specific PID - let env = ctx.data(); let pid: WasiProcessId = pid.into(); // Waiting for a process that is an explicit child will join it // meaning it will no longer be a sub-process of the main process let mut process = { - let mut inner = env.process.inner.write().unwrap(); + let mut inner = ctx.data().process.inner.write().unwrap(); let process = inner .children .iter() @@ -92,44 +168,53 @@ pub fn proc_join( // Otherwise it could be the case that we are waiting for a process // that is not a child of this process but may still be running if process.is_none() { - process = env.control_plane.get_process(pid); + process = ctx.data().control_plane.get_process(pid); } if let Some(process) = process { - let exit_code = wasi_try_ok!(__asyncify(&mut ctx, None, async move { - let code = process.join().await.unwrap_or_else(|_| Errno::Child.into()); - Ok(code) - }) - .map_err(|err| { - trace!( - %pid, - %err - ); - err - })?); - - trace!(ret_id = pid.raw(), exit_code = exit_code.raw()); - let env = ctx.data(); - { - let mut inner = env.process.inner.write().unwrap(); - inner.children.retain(|a| a.pid != pid); - } - - let memory = env.memory_view(&ctx); + // We can already set the process ID + wasi_try_mem_ok!(pid_ptr.write( + &memory, + OptionPid { + tag: OptionTag::Some, + pid: pid.raw(), + } + )); + + // Wait for the process to finish + let process2 = process.clone(); + let res = __asyncify_with_deep_sleep_ext::( + ctx, + None, + Duration::from_millis(50), + async move { process.join().await.unwrap_or_else(|_| Errno::Child.into()) }, + move |memory, store, res| { + let exit_code = res.unwrap_or_else(ExitCode::Errno); + + trace!(ret_id = pid.raw(), exit_code = exit_code.raw()); + { + let mut inner = process2.inner.write().unwrap(); + inner.children.retain(|a| a.pid != pid); + } - let option_pid = OptionPid { - tag: OptionTag::Some, - pid: pid.raw(), - }; - let status = JoinStatus { - tag: JoinStatusType::ExitNormal, - u: JoinStatusUnion { - exit_normal: exit_code.into(), + let memory = memory.view(store); + let status = JoinStatus { + tag: JoinStatusType::ExitNormal, + u: JoinStatusUnion { + exit_normal: exit_code.into(), + }, + }; + status_ptr + .write(&memory, status) + .map_err(mem_error_to_wasi) + .map_err(ExitCode::Errno)?; + Ok(()) }, + )?; + return match res { + AsyncifyAction::Finish(ctx) => ret_result(ctx), + AsyncifyAction::Unwind => Ok(Errno::Success), }; - wasi_try_mem_ok!(pid_ptr.write(&memory, option_pid)); - wasi_try_mem_ok!(status_ptr.write(&memory, status)); - return Ok(Errno::Success); } trace!(ret_id = pid.raw(), "status=nothing"); @@ -139,8 +224,10 @@ pub fn proc_join( let status = JoinStatus { tag: JoinStatusType::Nothing, - u: JoinStatusUnion { nothing: 0 }, + u: JoinStatusUnion { + nothing_errno: Errno::Success, + }, }; wasi_try_mem_ok!(status_ptr.write(&memory, status)); - Ok(Errno::Child) + ret_result(ctx) } diff --git a/lib/wasi/src/syscalls/wasix/sched_yield.rs b/lib/wasi/src/syscalls/wasix/sched_yield.rs index 9ae99a0a4d4..d6745761c6f 100644 --- a/lib/wasi/src/syscalls/wasix/sched_yield.rs +++ b/lib/wasi/src/syscalls/wasix/sched_yield.rs @@ -4,7 +4,9 @@ use crate::syscalls::*; /// ### `sched_yield()` /// Yields execution of the thread #[instrument(level = "trace", skip_all, ret, err)] -pub fn sched_yield(mut ctx: FunctionEnvMut<'_, WasiEnv>) -> Result { +pub fn sched_yield( + mut ctx: FunctionEnvMut<'_, WasiEnv>, +) -> Result { //trace!("wasi[{}:{}]::sched_yield", ctx.data().pid(), ctx.data().tid()); - thread_sleep_internal(ctx, 0) + thread_sleep_internal::(ctx, 0) } diff --git a/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs b/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs index 7de1e9afc77..85023fe30cf 100644 --- a/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs +++ b/lib/wasi/src/syscalls/wasix/stack_checkpoint.rs @@ -93,10 +93,12 @@ pub fn stack_checkpoint( let mut memory_stack_corrected = memory_stack.clone(); { let snapshot_offset: u64 = snapshot_offset.into(); - if snapshot_offset >= env.stack_start && snapshot_offset < env.stack_end { + if snapshot_offset >= env.layout.stack_lower + && (snapshot_offset + val_bytes.len() as u64) <= env.layout.stack_upper + { // Make sure its within the "active" part of the memory stack // (note - the area being written to might not go past the memory pointer) - let offset = env.stack_end - snapshot_offset; + let offset = env.layout.stack_upper - snapshot_offset; if (offset as usize) < memory_stack_corrected.len() { let left = memory_stack_corrected.len() - (offset as usize); let end = offset + (val_bytes.len().min(left) as u64); diff --git a/lib/wasi/src/syscalls/wasix/stack_restore.rs b/lib/wasi/src/syscalls/wasix/stack_restore.rs index ec978c57492..66c7ea753d8 100644 --- a/lib/wasi/src/syscalls/wasix/stack_restore.rs +++ b/lib/wasi/src/syscalls/wasix/stack_restore.rs @@ -40,11 +40,13 @@ pub fn stack_restore( // If the return value offset is within the memory stack then we need // to update it here rather than in the real memory + let val_bytes = val.to_ne_bytes(); let ret_val_offset = snapshot.user; - if ret_val_offset >= env.stack_start && ret_val_offset < env.stack_end { + if ret_val_offset >= env.layout.stack_lower + && (ret_val_offset + val_bytes.len() as u64) <= env.layout.stack_upper + { // Make sure its within the "active" part of the memory stack - let val_bytes = val.to_ne_bytes(); - let offset = env.stack_end - ret_val_offset; + let offset = env.layout.stack_upper - ret_val_offset; let end = offset + (val_bytes.len() as u64); if end as usize > memory_stack.len() { warn!( diff --git a/lib/wasi/src/syscalls/wasix/thread_join.rs b/lib/wasi/src/syscalls/wasix/thread_join.rs index 87631f6f149..327601ce45c 100644 --- a/lib/wasi/src/syscalls/wasix/thread_join.rs +++ b/lib/wasi/src/syscalls/wasix/thread_join.rs @@ -9,20 +9,35 @@ use crate::syscalls::*; /// /// * `tid` - Handle of the thread to wait on #[instrument(level = "debug", skip_all, fields(%join_tid), ret, err)] -pub fn thread_join( +pub fn thread_join( mut ctx: FunctionEnvMut<'_, WasiEnv>, join_tid: Tid, ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + if handle_rewind::(&mut ctx) { + return Ok(Errno::Success); + } let env = ctx.data(); let tid: WasiThreadId = join_tid.into(); let other_thread = env.process.get_thread(&tid); if let Some(other_thread) = other_thread { - wasi_try_ok!(__asyncify(&mut ctx, None, async move { - other_thread.join().await; - Ok(()) - })?); + let res = __asyncify_with_deep_sleep_ext::( + ctx, + None, + Duration::from_millis(50), + async move { + other_thread + .join() + .await + .map_err(|err| { + err.as_exit_code() + .unwrap_or(ExitCode::Errno(Errno::Unknown)) + }) + .unwrap_or_else(|a| a) + }, + |_, _, _| Ok(()), + )?; Ok(Errno::Success) } else { Ok(Errno::Success) diff --git a/lib/wasi/src/syscalls/wasix/thread_local_get.rs b/lib/wasi/src/syscalls/wasix/thread_local_get.rs index 8130728e2ca..30be3f5822b 100644 --- a/lib/wasi/src/syscalls/wasix/thread_local_get.rs +++ b/lib/wasi/src/syscalls/wasix/thread_local_get.rs @@ -9,7 +9,7 @@ use crate::syscalls::*; /// * `key` - Thread key that this local variable that was previous set #[instrument(level = "trace", skip_all, fields(%key, val = field::Empty), ret)] pub fn thread_local_get( - ctx: FunctionEnvMut<'_, WasiEnv>, + mut ctx: FunctionEnvMut<'_, WasiEnv>, key: TlKey, ret_val: WasmPtr, ) -> Errno { diff --git a/lib/wasi/src/syscalls/wasix/thread_sleep.rs b/lib/wasi/src/syscalls/wasix/thread_sleep.rs index b5d643a471b..19cf8240186 100644 --- a/lib/wasi/src/syscalls/wasix/thread_sleep.rs +++ b/lib/wasi/src/syscalls/wasix/thread_sleep.rs @@ -8,18 +8,21 @@ use crate::syscalls::*; /// /// * `duration` - Amount of time that the thread should sleep #[instrument(level = "debug", skip_all, fields(%duration), ret, err)] -pub fn thread_sleep( +pub fn thread_sleep( mut ctx: FunctionEnvMut<'_, WasiEnv>, duration: Timestamp, ) -> Result { - thread_sleep_internal(ctx, duration) + thread_sleep_internal::(ctx, duration) } -pub(crate) fn thread_sleep_internal( +pub(crate) fn thread_sleep_internal( mut ctx: FunctionEnvMut<'_, WasiEnv>, duration: Timestamp, ) -> Result { wasi_try_ok!(WasiEnv::process_signals_and_exit(&mut ctx)?); + if handle_rewind::(&mut ctx) { + return Ok(Errno::Success); + } let env = ctx.data(); @@ -31,14 +34,21 @@ pub(crate) fn thread_sleep_internal( if duration > 0 { let duration = Duration::from_nanos(duration as u64); let tasks = env.tasks().clone(); - wasi_try_ok!(__asyncify(&mut ctx, Some(duration), async move { - // using an infinite async sleep here means we don't have to write the same event - // handling loop code for signals and timeouts - InfiniteSleep::default().await; - unreachable!( - "the timeout or signals will wake up this thread even though it waits forever" - ) - })?); + + __asyncify_with_deep_sleep_ext::( + ctx, + Some(duration), + Duration::from_millis(50), + async move { + // using an infinite async sleep here means we don't have to write the same event + // handling loop code for signals and timeouts + InfiniteSleep::default().await; + unreachable!( + "the timeout or signals will wake up this thread even though it waits forever" + ) + }, + |_, _, _| Ok(()), + )?; } Ok(Errno::Success) } diff --git a/lib/wasi/src/syscalls/wasix/thread_spawn.rs b/lib/wasi/src/syscalls/wasix/thread_spawn.rs index a843bbb9401..2c0acb5e53d 100644 --- a/lib/wasi/src/syscalls/wasix/thread_spawn.rs +++ b/lib/wasi/src/syscalls/wasix/thread_spawn.rs @@ -1,5 +1,11 @@ use super::*; -use crate::syscalls::*; +use crate::{ + capture_snapshot, + os::task::thread::WasiMemoryLayout, + runtime::task_manager::{TaskWasm, TaskWasmRunProperties}, + syscalls::*, + WasiThreadHandle, +}; use wasmer::Memory; use wasmer_wasix_types::wasi::ThreadStart; @@ -24,7 +30,7 @@ pub fn thread_spawn( ret_tid: WasmPtr, ) -> Errno { // Create the thread - let tid = wasi_try!(thread_spawn_internal(&ctx, start_ptr)); + let tid = wasi_try!(thread_spawn_internal(&mut ctx, start_ptr)); // Success let memory = ctx.data().memory_view(&ctx); @@ -33,7 +39,7 @@ pub fn thread_spawn( } pub(crate) fn thread_spawn_internal( - ctx: &FunctionEnvMut<'_, WasiEnv>, + ctx: &mut FunctionEnvMut<'_, WasiEnv>, start_ptr: WasmPtr, M>, ) -> Result { // Now we use the environment and memory references @@ -41,20 +47,36 @@ pub(crate) fn thread_spawn_internal( let memory = env.memory_view(&ctx); let runtime = env.runtime.clone(); let tasks = env.tasks().clone(); + let start_ptr_offset = start_ptr.offset(); + + // We extract the memory which will be passed to the thread + let thread_memory = env.memory_clone(); // Read the properties about the stack which we will use for asyncify - let start = start_ptr.read(&memory).map_err(mem_error_to_wasi)?; - let stack_start: u64 = start.stack_start.try_into().map_err(|_| Errno::Overflow)?; - let stack_size: u64 = start.stack_size.try_into().map_err(|_| Errno::Overflow)?; - let stack_base = stack_start - stack_size; + let layout = { + let start: ThreadStart = start_ptr.read(&memory).map_err(mem_error_to_wasi)?; + let stack_upper: u64 = start.stack_upper.try_into().map_err(|_| Errno::Overflow)?; + let stack_size: u64 = start.stack_size.try_into().map_err(|_| Errno::Overflow)?; + let guard_size: u64 = start.guard_size.try_into().map_err(|_| Errno::Overflow)?; + let tls_base: u64 = start.tls_base.try_into().map_err(|_| Errno::Overflow)?; + let stack_lower = stack_upper - stack_size; + + tracing::trace!(%stack_upper, %stack_lower, %stack_size, %guard_size, %tls_base); + + WasiMemoryLayout { + stack_upper, + stack_lower, + guard_size, + stack_size, + } + }; // Create the handle that represents this thread let mut thread_handle = match env.process.new_thread() { - Ok(h) => h, + Ok(h) => Arc::new(h), Err(err) => { error!( - %stack_base, - caller_id = current_caller_id().raw(), + stack_base = layout.stack_lower, "failed to create thread handle", ); // TODO: evaluate the appropriate error code, document it in the spec. @@ -64,68 +86,76 @@ pub(crate) fn thread_spawn_internal( let thread_id: Tid = thread_handle.id().into(); Span::current().record("tid", thread_id); - let mut store = ctx.data().runtime.new_store(); - - // This function takes in memory and a store and creates a context that - // can be used to call back into the process - let create_ctx = { - let state = env.state.clone(); - let wasi_env = env.duplicate(); - let thread = thread_handle.as_thread(); - move |mut store: Store, module: Module, memory: Memory| { - // We need to reconstruct some things - let module = module; - // Build the context object and import the memory - let mut ctx = WasiFunctionEnv::new(&mut store, wasi_env.duplicate()); - { - let env = ctx.data_mut(&mut store); - env.thread = thread.clone(); - env.stack_end = stack_base; - env.stack_start = stack_start; - } - - let (mut import_object, init) = - import_object_for_all_wasi_versions(&module, &mut store, &ctx.env); - import_object.define("env", "memory", memory.clone()); + // We capture some local variables + let state = env.state.clone(); + let mut thread_env = env.clone(); + thread_env.thread = thread_handle.as_thread(); + thread_env.layout = layout; + + // TODO: Currently asynchronous threading does not work with multi + // threading but it does work for the main thread. This will + // require more work to find out why. + thread_env.enable_deep_sleep = if cfg!(feature = "js") { + false + } else { + env.capable_of_deep_sleep() + }; - let instance = match Instance::new(&mut store, &module, &import_object) { - Ok(a) => a, - Err(err) => { - error!("failed - create instance failed: {}", err); - return Err(Errno::Noexec as u32); - } - }; + // This next function gets a context for the local thread and then + // calls into the process + let mut execute_module = { + let thread_handle = thread_handle; + move |ctx: WasiFunctionEnv, mut store: Store| { + // Call the thread + call_module::(ctx, store, start_ptr_offset, thread_handle, None) + } + }; - init(&instance, &store).unwrap(); + // If the process does not export a thread spawn function then obviously + // we can't spawn a background thread + if env.inner().thread_spawn.is_none() { + warn!("thread failed - the program does not export a `wasi_thread_start` function"); + return Err(Errno::Notcapable); + } + let thread_module = env.inner().module_clone(); + let snapshot = capture_snapshot(&mut ctx.as_store_mut()); + let spawn_type = + crate::runtime::SpawnMemoryType::ShareMemory(thread_memory, ctx.as_store_ref()); - // Set the current thread ID - ctx.data_mut(&mut store).inner = - Some(WasiInstanceHandles::new(memory, &store, instance)); - Ok(WasiThreadContext { - ctx, - store: RefCell::new(store), - }) - } + // Now spawn a thread + trace!("threading: spawning background thread"); + let run = move |props: TaskWasmRunProperties| { + execute_module(props.ctx, props.store); }; + tasks + .task_wasm( + TaskWasm::new(Box::new(run), thread_env, thread_module, false) + .with_snapshot(&snapshot) + .with_memory(spawn_type), + ) + .map_err(Into::::into)?; + + // Success + Ok(thread_id) +} - // We need a copy of the process memory and a packaged store in order to - // launch threads and reactors - let thread_memory = ctx - .data() - .memory() - .clone_in_store(&ctx, &mut store) - .ok_or_else(|| { - error!("failed - the memory could not be cloned"); - Errno::Notcapable - })?; +/// Calls the module +fn call_module( + mut ctx: WasiFunctionEnv, + mut store: Store, + start_ptr_offset: M::Offset, + thread_handle: Arc, + rewind_state: Option<(RewindState, Result<(), Errno>)>, +) -> Result { + let env = ctx.data(&store); + let tasks = env.tasks().clone(); // This function calls into the module - let start_ptr_offset = start_ptr.offset(); - let call_module = move |ctx: &WasiFunctionEnv, store: &mut Store| { + let call_module_internal = move |env: &WasiFunctionEnv, store: &mut Store| { // We either call the reactor callback or the thread spawn callback //trace!("threading: invoking thread callback (reactor={})", reactor); - let spawn = ctx.data(&store).inner().thread_spawn.clone().unwrap(); - let tid = ctx.data(&store).tid(); + let spawn = env.data(&store).inner().thread_spawn.clone().unwrap(); + let tid = env.data(&store).tid(); let call_ret = spawn.call( store, tid.raw().try_into().map_err(|_| Errno::Overflow).unwrap(), @@ -144,6 +174,10 @@ pub(crate) fn thread_spawn_internal( Errno::Noexec }; } + Ok(WasiError::DeepSleep(deep)) => { + trace!("entered a deep sleep"); + return Err(deep); + } Ok(WasiError::UnknownWasiVersion) => { debug!("failed as wasi version is unknown",); ret = Errno::Noexec; @@ -157,103 +191,58 @@ pub(crate) fn thread_spawn_internal( trace!("callback finished (ret={})", ret); // Clean up the environment - ctx.cleanup(store, Some(ret.into())); + env.cleanup(store, Some(ret.into())); // Return the result - ret as u32 + Ok(ret as u32) }; - // This next function gets a context for the local thread and then - // calls into the process - let mut execute_module = { - let state = env.state.clone(); - move |store: &mut Option, module: Module, memory: &mut Option| { - // We capture the thread handle here, it is used to notify - // anyone that is interested when this thread has terminated - let _captured_handle = Box::new(&mut thread_handle); - - // Given that it is not safe to assume this delegate will run on the - // same thread we need to capture a simple process that will create - // context objects on demand and reuse them - let caller_id = current_caller_id(); - - // We loop because read locks are held while functions run which need - // to be relocked in the case of a miss hit. - loop { - let thread = { - let guard = state.threading.read().unwrap(); - guard.thread_ctx.get(&caller_id).cloned() - }; - if let Some(thread) = thread { - let mut store = thread.store.borrow_mut(); - let ret = call_module(&thread.ctx, store.deref_mut()); + // If we need to rewind then do so + if let Some((mut rewind_state, trigger_res)) = rewind_state { + if let Err(exit_code) = rewind_state.rewinding_finish::(&ctx, &mut store, trigger_res) { + return Err(exit_code.into()); + } + let res = rewind::( + ctx.env.clone().into_mut(&mut store), + rewind_state.memory_stack, + rewind_state.rewind_stack, + rewind_state.store_data, + ); + if res != Errno::Success { + return Err(res); + } + } - { - let mut guard = state.threading.write().unwrap(); - guard.thread_ctx.remove(&caller_id); - } + // Now invoke the module + let ret = call_module_internal(&ctx, &mut store); - return ret; + // If it went to deep sleep then we need to handle that + match ret { + Ok(ret) => { + // Frees the handle so that it closes + drop(thread_handle); + Ok(ret as Pid) + } + Err(deep) => { + // Create the callback that will be invoked when the thread respawns after a deep sleep + let rewind = deep.rewind; + let respawn = { + let tasks = tasks.clone(); + move |ctx, store, trigger_res| { + // Call the thread + call_module::( + ctx, + store, + start_ptr_offset, + thread_handle, + Some((rewind, trigger_res)), + ); } + }; - // Otherwise we need to create a new context under a write lock - debug!( - "encountered a new caller (ref={}) - creating WASM execution context...", - caller_id.raw() - ); - - // We can only create the context once per thread - let memory = match memory.take() { - Some(m) => m, - None => { - debug!("failed - memory can only be consumed once per context creation"); - return Errno::Noexec as u32; - } - }; - let store = match store.take() { - Some(s) => s, - None => { - debug!("failed - store can only be consumed once per context creation"); - return Errno::Noexec as u32; - } - }; - - // Now create the context and hook it up - let mut guard = state.threading.write().unwrap(); - let ctx = match create_ctx(store, module.clone(), memory) { - Ok(c) => c, - Err(err) => { - return err; - } - }; - guard.thread_ctx.insert(caller_id, Arc::new(ctx)); - } + /// Spawns the WASM process after a trigger + tasks.resume_wasm_after_poller(Box::new(respawn), ctx, store, deep.trigger); + Err(Errno::Unknown) } - }; - - // If the process does not export a thread spawn function then obviously - // we can't spawn a background thread - if env.inner().thread_spawn.is_none() { - warn!("thread failed - the program does not export a `wasi_thread_start` function"); - return Err(Errno::Notcapable); } - let spawn_type = crate::runtime::SpawnType::NewThread(thread_memory); - - // Now spawn a thread - trace!("threading: spawning background thread"); - let thread_module = env.inner().instance.module().clone(); - let tasks2 = tasks.clone(); - - let task = move |store, thread_module, mut thread_memory| { - // FIXME: should not use unwrap() here! (initializiation refactor) - let mut store = Some(store); - execute_module(&mut store, thread_module, &mut thread_memory); - }; - - tasks - .task_wasm(Box::new(task), store, thread_module, spawn_type) - .map_err(Into::::into)?; - - // Success - Ok(thread_id) } diff --git a/lib/wasi/src/utils/store.rs b/lib/wasi/src/utils/store.rs index 161ae58d4f8..047b09d9aef 100644 --- a/lib/wasi/src/utils/store.rs +++ b/lib/wasi/src/utils/store.rs @@ -1,5 +1,5 @@ /// A snapshot that captures the runtime state of an instance. -#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +#[derive(Default, serde::Serialize, serde::Deserialize, Clone, Debug)] pub struct InstanceSnapshot { /// Values of all globals, indexed by the same index used in Webassembly. pub globals: Vec, diff --git a/tests/compilers/traps.rs b/tests/compilers/traps.rs index 9b6ea9d070e..1f369dd676e 100644 --- a/tests/compilers/traps.rs +++ b/tests/compilers/traps.rs @@ -256,7 +256,7 @@ fn trap_start_function_import(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let sig = FunctionType::new(vec![], vec![]); let func = Function::new(&mut store, &sig, |_| Err(RuntimeError::new("user trap"))); let err = Instance::new( @@ -297,7 +297,7 @@ fn rust_panic_import(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let sig = FunctionType::new(vec![], vec![]); let func = Function::new(&mut store, &sig, |_| panic!("this is a panic")); let f0 = Function::new_typed(&mut store, || panic!("this is another panic")); @@ -342,7 +342,7 @@ fn rust_panic_start_function(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let sig = FunctionType::new(vec![], vec![]); let func = Function::new(&mut store, &sig, |_| panic!("this is a panic")); let err = panic::catch_unwind(AssertUnwindSafe(|| { @@ -388,7 +388,7 @@ fn mismatched_arguments(config: crate::Config) -> Result<()> { ) "#; - let module = Module::new(&store, &binary)?; + let module = Module::new(&store, binary)?; let instance = Instance::new(&mut store, &module, &imports! {})?; let func: &Function = instance.exports.get("foo")?; assert_eq!( diff --git a/tests/integration/cli/tests/snapshot.rs b/tests/integration/cli/tests/snapshot.rs index d4c489304d2..2ef474812f4 100644 --- a/tests/integration/cli/tests/snapshot.rs +++ b/tests/integration/cli/tests/snapshot.rs @@ -44,6 +44,13 @@ pub struct TestSpec { pub debug_output: bool, pub enable_threads: bool, pub enable_network: bool, + #[serde(skip_serializing_if = "is_false")] + #[serde(default)] + pub enable_async_threads: bool, +} + +fn is_false(b: &bool) -> bool { + *b == false } static WEBC_BASH: &[u8] = @@ -54,10 +61,10 @@ static WEBC_COREUTILS_11: &[u8] = include_bytes!("./webc/coreutils-1.0.11-9d7746ca-694f-11ed-b932-dead3543c068.webc"); static WEBC_DASH: &[u8] = include_bytes!("./webc/dash-1.0.16-bd931010-c134-4785-9423-13c0a0d49d90.webc"); -static WEBC_PYTHON: &[u8] = include_bytes!("./webc/python-0.1.0.webc"); -static WEBC_WEB_SERVER: &[u8] = - include_bytes!("./webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc"); -static WEBC_WASMER_SH: &[u8] = +static WEBC_PYTHON: &'static [u8] = include_bytes!("./webc/python-0.1.0.webc"); +static WEBC_WEB_SERVER: &'static [u8] = + include_bytes!("./webc/static-web-server-1.0.92-22ccedaa-3f96-4de0-b24a-ef48ade8151b.webc"); +static WEBC_WASMER_SH: &'static [u8] = include_bytes!("./webc/wasmer-sh-1.0.63-dd3d67d1-de94-458c-a9ee-caea3b230ccf.webc"); impl std::fmt::Debug for TestSpec { @@ -128,6 +135,7 @@ impl TestBuilder { debug_output: false, enable_threads: true, enable_network: false, + enable_async_threads: false, }, } } @@ -137,6 +145,11 @@ impl TestBuilder { self } + pub fn with_async_threads(mut self) -> Self { + self.spec.enable_async_threads = true; + self + } + pub fn args, S: AsRef>(mut self, args: I) -> Self { let args = args.into_iter().map(|s| s.as_ref().to_string()); self.spec.cli_args.extend(args); @@ -283,6 +296,10 @@ pub fn run_test_with(spec: TestSpec, code: &[u8], with: RunWith) -> TestResult { } cmd.arg("--allow-multiple-wasi-versions"); + if spec.enable_async_threads { + cmd.arg("--enable-async-threads"); + } + for pkg in &spec.use_packages { cmd.args(["--use", &pkg]); } @@ -448,6 +465,17 @@ fn test_snapshot_condvar() { assert_json_snapshot!(snapshot); } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_condvar_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .debug_output(true) + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-condvar.wasm")); + assert_json_snapshot!(snapshot); +} + // Test that the expected default directories are present. #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] #[test] @@ -497,6 +525,16 @@ fn test_snapshot_epoll() { assert_json_snapshot!(snapshot); } +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_epoll_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-epoll.wasm")); + assert_json_snapshot!(snapshot); +} + #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] #[test] fn test_snapshot_file_copy() { @@ -552,6 +590,84 @@ fn test_snapshot_minimodem_rx() { assert_json_snapshot!(snapshot); } +fn test_run_http_request( + port: u16, + what: &str, + expected_size: Option, +) -> Result { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()?; + + let http_get = move |url, max_retries: i32| { + rt.block_on(async move { + for n in 0..(max_retries.max(1)) { + println!("http request: {}", &url); + tokio::select! { + resp = reqwest::get(&url) => { + let resp = match resp { + Ok(a) => a, + Err(_) if n < max_retries => { + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + continue; + } + Err(err) => return Err(err.into()) + }; + if resp.status().is_success() == false { + return Err(anyhow::format_err!("incorrect status code: {}", resp.status())); + } + return Ok(resp.bytes().await?); + } + _ = tokio::time::sleep(std::time::Duration::from_secs(2)) => { + eprintln!("retrying request... ({} attempts)", (n+1)); + continue; + } + } + } + Err(anyhow::format_err!("timeout while performing HTTP request")) + }) + }; + + let expected_size = match expected_size { + None => { + let url = format!("http://localhost:{}/{}.size", port, what); + let expected_size = usize::from_str_radix( + String::from_utf8_lossy(http_get(url, 50)?.as_ref()).trim(), + 10, + )?; + if expected_size == 0 { + return Err(anyhow::format_err!("There was no data returned")); + } + expected_size + } + Some(s) => s, + }; + println!("expected_size: {}", expected_size); + + let url = format!("http://localhost:{}/{}", port, what); + let reference_data = http_get(url.clone(), 50)?; + for _ in 0..20 { + let test_data = http_get(url.clone(), 2)?; + println!("actual_size: {}", test_data.len()); + + if expected_size != test_data.len() { + return Err(anyhow::format_err!( + "The actual size and expected size does not match {} vs {}", + test_data.len(), + expected_size + )); + } + if test_data + .iter() + .zip(reference_data.iter()) + .any(|(a, b)| a != b) + { + return Err(anyhow::format_err!("The returned data is inconsistent")); + } + } + Ok(0) +} + #[cfg_attr( any(target_env = "musl", target_os = "macos", target_os = "windows"), ignore @@ -576,94 +692,62 @@ fn test_snapshot_unix_pipe() { #[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] #[test] fn test_snapshot_web_server() { - let with_inner = || { - let rt = tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build()?; - - let http_get = |url, max_retries| { - rt.block_on(async move { - for n in 0..(max_retries+1) { - println!("http request: {}", url); - tokio::select! { - resp = reqwest::get(url) => { - let resp = match resp { - Ok(a) => a, - Err(_) if n < max_retries => { - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - continue; - } - Err(err) => return Err(err.into()) - }; - if resp.status().is_success() == false { - return Err(anyhow::format_err!("incorrect status code: {}", resp.status())); - } - return Ok(resp.bytes().await?); - } - _ = tokio::time::sleep(std::time::Duration::from_secs(1)) => { - eprintln!("retrying request... ({} attempts)", n); - continue; - } - } - } - Err(anyhow::format_err!("timeout while performing HTTP request")) - }) - }; - - let expected_size = usize::from_str_radix( - String::from_utf8_lossy(http_get("http://localhost:7777/main.js.size", 50)?.as_ref()) - .trim(), - 10, - )?; - if expected_size == 0 { - return Err(anyhow::format_err!("There was no data returned")); - } - println!("expected_size: {}", expected_size); - - let reference_data = http_get("http://localhost:7777/main.js", 0)?; - for _ in 0..20 { - let test_data = http_get("http://localhost:7777/main.js", 0)?; - println!("actual_size: {}", test_data.len()); - - if expected_size != test_data.len() { - return Err(anyhow::format_err!( - "The actual size and expected size does not match {} vs {}", - test_data.len(), - expected_size - )); - } - if test_data - .iter() - .zip(reference_data.iter()) - .any(|(a, b)| a != b) - { - return Err(anyhow::format_err!("The returned data is inconsistent")); - } - } - - Ok(0) - }; + let name = function!(); + let port = 7777; let with = move |mut child: Child| { - let ret = with_inner(); + let ret = test_run_http_request(port, "main.js", None); child.kill().ok(); ret }; - let snapshot = TestBuilder::new() - .with_name(function!()) + let script = format!( + r#" +cat /public/main.js | wc -c > /public/main.js.size +rm -f /cfg/config.toml +/bin/webserver --log-level warn --root /public --port {}"#, + port + ); + let builder = TestBuilder::new() + .with_name(name) .enable_network(true) - .include_static_package("sharrattj/static-web-server@1.0.8", WEBC_WEB_SERVER) + .include_static_package("sharrattj/static-web-server@1.0.92", WEBC_WEB_SERVER) .include_static_package("sharrattj/wasmer-sh@1.0.63", WEBC_WASMER_SH) .use_coreutils() .use_pkg("sharrattj/wasmer-sh") - .stdin_str( - r#" -cat /public/main.js | wc -c > /public/main.js.size -rm -f /cfg/config.toml -/bin/webserver --log-level warn --root /public --port 7777"#, - ) - .run_wasm_with(include_bytes!("./wasm/dash.wasm"), Box::new(with)); + .stdin_str(script); + + let snapshot = builder.run_wasm_with(include_bytes!("./wasm/dash.wasm"), Box::new(with)); + assert_json_snapshot!(snapshot); +} + +#[cfg_attr( + any(target_env = "musl", target_os = "macos", target_os = "windows"), + ignore +)] +#[test] +fn test_snapshot_web_server_async() { + let name = function!(); + let port = 7778; + + let with = move |mut child: Child| { + let ret = test_run_http_request(port, "null", Some(0)); + child.kill().ok(); + ret + }; + + let builder = TestBuilder::new() + .with_name(name) + .with_async_threads() + .enable_network(true) + .arg("--root") + .arg("/dev") + .arg("--log-level") + .arg("warn") + .arg("--port") + .arg(&format!("{}", port)); + + let snapshot = builder.run_wasm_with(include_bytes!("./wasm/web-server.wasm"), Box::new(with)); assert_json_snapshot!(snapshot); } @@ -679,6 +763,20 @@ fn test_snapshot_fork_and_exec() { assert_json_snapshot!(snapshot); } +// The ability to fork the current process and run a different image but retain +// the existing open file handles (which is needed for stdin and stdout redirection) +#[cfg(not(target_os = "windows"))] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_fork_and_exec_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-execve.wasm")); + assert_json_snapshot!(snapshot); +} + // longjmp is used by C programs that save and restore the stack at specific // points - this functionality is often used for exception handling #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] @@ -714,6 +812,18 @@ fn test_snapshot_fork() { assert_json_snapshot!(snapshot); } +// Simple fork example that is a crude multi-threading implementation - used by `dash` +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_fork_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-fork.wasm")); + assert_json_snapshot!(snapshot); +} + // Uses the `fd_pipe` syscall to create a bidirection pipe with two file // descriptors then forks the process to write and read to this pipe. #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] @@ -739,6 +849,20 @@ fn test_snapshot_longjump_fork() { assert_json_snapshot!(snapshot); } +// Performs a longjmp of a stack that was recorded before the fork. +// This test ensures that the stacks that have been recorded are preserved +// after a fork. +// The behavior is needed for `dash` +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_longjump_fork_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-fork-longjmp.wasm")); + assert_json_snapshot!(snapshot); +} + // full multi-threading with shared memory and shared compiled modules #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] #[test] @@ -776,6 +900,19 @@ fn test_snapshot_threaded_memory() { // full multi-threading with shared memory and shared compiled modules #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] #[test] +fn test_snapshot_multithreading_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .debug_output(true) + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-multi-threading.wasm")); + assert_json_snapshot!(snapshot); +} + +// full multi-threading with shared memory and shared compiled modules +#[cfg(target_os = "linux")] +#[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] +#[test] fn test_snapshot_sleep() { let snapshot = TestBuilder::new() .with_name(function!()) @@ -783,6 +920,18 @@ fn test_snapshot_sleep() { assert_json_snapshot!(snapshot); } +// full multi-threading with shared memory and shared compiled modules +#[cfg(target_os = "linux")] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_sleep_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-sleep.wasm")); + assert_json_snapshot!(snapshot); +} + // Uses `posix_spawn` to launch a sub-process and wait on it to exit #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] #[test] @@ -794,6 +943,19 @@ fn test_snapshot_process_spawn() { assert_json_snapshot!(snapshot); } +// Uses `posix_spawn` to launch a sub-process and wait on it to exit +#[cfg(not(target_os = "windows"))] +#[cfg(not(any(target_env = "musl", target_os = "macos", target_os = "windows")))] +#[test] +fn test_snapshot_process_spawn_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-spawn.wasm")); + assert_json_snapshot!(snapshot); +} + // FIXME: re-enable - hangs on windows and macos // Connects to 8.8.8.8:53 over TCP to verify TCP clients work // #[test] @@ -838,6 +1000,19 @@ fn test_snapshot_vfork() { assert_json_snapshot!(snapshot); } +// Tests that lightweight forking that does not copy the memory but retains the +// open file descriptors works correctly. Uses asynchronous threading +#[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] +#[test] +fn test_snapshot_vfork_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .use_coreutils() + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-vfork.wasm")); + assert_json_snapshot!(snapshot); +} + #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] #[test] fn test_snapshot_signals() { @@ -847,6 +1022,17 @@ fn test_snapshot_signals() { assert_json_snapshot!(snapshot); } +#[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] +#[test] +fn test_snapshot_signals_async() { + let snapshot = TestBuilder::new() + .with_name(function!()) + .with_async_threads() + .run_wasm(include_bytes!("./wasm/example-signal.wasm")); + assert_json_snapshot!(snapshot); +} + +#[cfg(target_os = "linux")] #[cfg_attr(any(target_env = "musl", target_os = "windows"), ignore)] #[test] fn test_snapshot_dash_echo() { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap index e478e2fb501..59a8622936a 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_dash.snap @@ -27,7 +27,7 @@ expression: snapshot "Success": { "stdout": "hi\n", "stderr": "test.wasm-5.1# # # test.wasm-5.1# exit\n", - "exit_code": 78 + "exit_code": 0 } } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap index f66a3777626..a36f562ca8f 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_bash_pipe.snap @@ -1,6 +1,6 @@ --- source: tests/integration/cli/tests/snapshot.rs -assertion_line: 487 +assertion_line: 1064 expression: snapshot --- { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_condvar_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_condvar_async.snap new file mode 100644 index 00000000000..8754d7b0df7 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_condvar_async.snap @@ -0,0 +1,23 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_condvar_async", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "debug_output": true, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "condvar1 thread spawn\ncondvar1 thread started\ncondvar1 thread sleep(1sec) start\ncondvar loop\ncondvar wait\ncondvar1 thread sleep(1sec) end\ncondvar1 thread set condition\ncondvar1 thread notify\ncondvar woken\ncondvar parent done\ncondvar1 thread exit\nall done\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap index 648a9c66407..3cff6cc3c58 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_dash_echo_to_cat.snap @@ -1,6 +1,6 @@ --- source: tests/integration/cli/tests/snapshot.rs -assertion_line: 456 +assertion_line: 976 expression: snapshot --- { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll_async.snap new file mode 100644 index 00000000000..5c67d041f36 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_epoll_async.snap @@ -0,0 +1,24 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 521 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_epoll_async", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "EFD_NONBLOCK:4\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\nsuccess write to efd, write 8 bytes(4)\nsuccess read from efd, read 8 bytes(4)\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap index 45723256eb6..b295fd946e4 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve.snap @@ -1,6 +1,6 @@ --- source: tests/integration/cli/tests/snapshot.rs -assertion_line: 381 +assertion_line: 561 expression: snapshot --- { @@ -21,8 +21,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nChild(2) exited with 0\nexecve: echo hi-from-parent\nhi-from-parent\n", - "stderr": "", + "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nhi-from-parent\n", + "stderr": "Child(2) exited with 0\nexecve: echo hi-from-parent\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve_async.snap new file mode 100644 index 00000000000..c8292814781 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_execve_async.snap @@ -0,0 +1,30 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 381 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_execve_async", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nChild(2) exited with 0\nexecve: echo hi-from-parent\nhi-from-parent\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap index da92ed84112..84cf5ecd019 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap @@ -20,8 +20,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Parent has x = 0\nParent memory is good\nChild has x = 2\nChild memory is good\nChild(2) exited with 3\n", - "stderr": "", + "stdout": "Parent has x = 0\nParent memory is good\nChild(2) exited with 3\n", + "stderr": "Child has x = 2\nChild memory is good\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap index 047af398289..541d37db292 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec.snap @@ -20,8 +20,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nChild(2) exited with 0\nexecve: echo hi-from-parent\nhi-from-parent\n", - "stderr": "", + "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nhi-from-parent\n", + "stderr": "Child(2) exited with 0\nexecve: echo hi-from-parent\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec_async.snap new file mode 100644 index 00000000000..e5eddf9e4a4 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_and_exec_async.snap @@ -0,0 +1,29 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_fork_and_exec_async", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "Main program started\nexecve: echo hi-from-child\nhi-from-child\nhi-from-parent\n", + "stderr": "Child(2) exited with 0\nexecve: echo hi-from-parent\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_async.snap new file mode 100644 index 00000000000..d3f4f8b8f6b --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork_async.snap @@ -0,0 +1,29 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_fork_async", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "Parent has x = 0\nParent memory is good\nChild(2) exited with 3\n", + "stderr": "Child has x = 2\nChild memory is good\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap index 23f04c937ac..1d2534a281a 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump.snap @@ -20,8 +20,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "(A1)\n(B1)\n(A2) r=10001\n(B2) r=20001\n(A3) r=10002\n(B3) r=20002\n(A4) r=10003\n", - "stderr": "", + "stdout": "(A1)\n(A2) r=10001\n(A3) r=10002\n(A4) r=10003\n", + "stderr": "(B1)\n(B2) r=20001\n(B3) r=20002\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap index 8585a2ebaeb..c6af78a74be 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork.snap @@ -14,8 +14,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Parent memory is good\nParent memory after longjmp is good\nParent has x = 0\nChild memory is good\nChild memory after longjmp is good\nChild has x = 2\nChild(2) exited with 5\n", - "stderr": "", + "stdout": "Parent memory is good\nParent memory after longjmp is good\nParent has x = 0\nChild(2) exited with 5\n", + "stderr": "Child memory is good\nChild memory after longjmp is good\nChild has x = 2\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork_async.snap new file mode 100644 index 00000000000..c1ea7559917 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_longjump_fork_async.snap @@ -0,0 +1,23 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_longjump_fork_async", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "Parent memory is good\nParent memory after longjmp is good\nParent has x = 0\nChild(2) exited with 5\n", + "stderr": "Child memory is good\nChild memory after longjmp is good\nChild has x = 2\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_multithreading_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_multithreading_async.snap new file mode 100644 index 00000000000..0d601547a9d --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_multithreading_async.snap @@ -0,0 +1,23 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_multithreading_async", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "debug_output": true, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "thread 1 started\nthread 2 started\nthread 3 started\nthread 4 started\nthread 5 started\nthread 6 started\nthread 7 started\nthread 8 started\nthread 9 started\nwaiting for threads\nthread 1 finished\nthread 2 finished\nthread 3 finished\nthread 4 finished\nthread 5 finished\nthread 6 finished\nthread 7 finished\nthread 8 finished\nthread 9 finished\nall done\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap index d725ddc2450..30d81a71f71 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_pipes.snap @@ -20,8 +20,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "this text should be printed by the child\nthis text should be printed by the parent\n", - "stderr": "", + "stdout": "this text should be printed by the parent\n", + "stderr": "this text should be printed by the child\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap index c3a1e43b81b..3d7dcecc0f0 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn.snap @@ -20,8 +20,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Child pid: 2\nhi\nChild status 0\n", - "stderr": "", + "stdout": "hi\n", + "stderr": "Child pid: 2\nChild status 0\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn_async.snap new file mode 100644 index 00000000000..f4132f5242a --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_process_spawn_async.snap @@ -0,0 +1,29 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_process_spawn_async", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "hi\n", + "stderr": "Child pid: 2\nChild status 0\n", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_signals_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_signals_async.snap new file mode 100644 index 00000000000..4cbd9784bd1 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_signals_async.snap @@ -0,0 +1,24 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 537 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_signals_async", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "received SIGHUP\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap index 175d6d79392..f99ec4a4035 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep.snap @@ -14,7 +14,7 @@ expression: snapshot }, "result": { "Success": { - "stdout": "", + "stdout": "before\ndone\n", "stderr": "", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep_async.snap new file mode 100644 index 00000000000..df678e1e7f5 --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_sleep_async.snap @@ -0,0 +1,23 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_sleep_async", + "use_packages": [], + "include_webcs": [], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "before\ndone\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_tcp_client.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_tcp_client.snap index 4595e1d8f63..fbe7f5050dc 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_tcp_client.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_tcp_client.snap @@ -10,9 +10,8 @@ expression: snapshot "sharrattj/coreutils" ], "cli_args": [], - "stdin": null, "debug_output": false, - "enable_threads": true + "enable_threads": true, }, "result": { "Success": { diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_tokio.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_tokio.snap index e2c9ea188ec..a2e4df7d880 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_tokio.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_tokio.snap @@ -1,6 +1,6 @@ --- source: tests/integration/cli/tests/snapshot.rs -assertion_line: 543 +assertion_line: 683 expression: snapshot --- { @@ -15,8 +15,8 @@ expression: snapshot }, "result": { "Success": { - "stdout": "", - "stderr": "step-1\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-2\nstep-3\ndata match verified (len=128)\nstep-4\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-5\nstep-A1\nstep-B1\nstep-B2\nstep-A2\ndata match verified (len=128)\nstep-A3\nstep-B3\nstep-B4\nstep-A4\ndata match verified (len=128)\nstep-A5\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-A6\nstep-A7\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-A8\nall done\n", + "stdout": "step-B1\nstep-B2\nstep-B3\nstep-B4\n", + "stderr": "step-1\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-2\nstep-3\ndata match verified (len=128)\nstep-4\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-5\nstep-A1\nstep-A2\ndata match verified (len=128)\nstep-A3\nstep-A4\ndata match verified (len=128)\nstep-A5\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-A6\nstep-A7\nreceived ErrorKind::WouldBlock - receiving on an empty channel\nstep-A8\nall done\n", "exit_code": 0 } } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap index f78e8083eff..27c670cb333 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork.snap @@ -20,7 +20,7 @@ expression: snapshot }, "result": { "Success": { - "stdout": "Parent waiting on Child(2)\nChild(2) exited with 0\n", + "stdout": "Parent waiting on Child(2)\nChild(2) exited with 10\n", "stderr": "", "exit_code": 0 } diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork_async.snap new file mode 100644 index 00000000000..a26b4f6464d --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_vfork_async.snap @@ -0,0 +1,29 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_vfork_async", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "Parent waiting on Child(2)\nChild(2) exited with 10\n", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server.snap index 4596b492172..99b755b8699 100644 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server.snap +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server.snap @@ -1,6 +1,6 @@ --- source: tests/integration/cli/tests/snapshot.rs -assertion_line: 492 +assertion_line: 583 expression: snapshot --- { @@ -12,7 +12,7 @@ expression: snapshot ], "include_webcs": [ { - "name": "sharrattj/static-web-server@1.0.8" + "name": "sharrattj/static-web-server@1.0.92" }, { "name": "sharrattj/wasmer-sh@1.0.63" diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server_async.snap b/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server_async.snap new file mode 100644 index 00000000000..64d2b27ed0d --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_web_server_async.snap @@ -0,0 +1,31 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 590 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_web_server_async", + "use_packages": [], + "include_webcs": [], + "cli_args": [ + "--root", + "/dev", + "--log-level", + "warn", + "--port", + "7778" + ], + "debug_output": false, + "enable_threads": true, + "enable_network": true, + "enable_async_threads": true + }, + "result": { + "Success": { + "stdout": "", + "stderr": "", + "exit_code": 0 + } + } +} diff --git a/tests/integration/cli/tests/wasm/example-execve.wasm b/tests/integration/cli/tests/wasm/example-execve.wasm index 5caeb2fde26..9444f7688ea 100644 Binary files a/tests/integration/cli/tests/wasm/example-execve.wasm and b/tests/integration/cli/tests/wasm/example-execve.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm b/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm index e2e847c693f..532a887ff1c 100644 Binary files a/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm and b/tests/integration/cli/tests/wasm/example-fork-longjmp.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-fork.wasm b/tests/integration/cli/tests/wasm/example-fork.wasm index d805b180fac..83b45f5fc2c 100755 Binary files a/tests/integration/cli/tests/wasm/example-fork.wasm and b/tests/integration/cli/tests/wasm/example-fork.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-longjmp.wasm b/tests/integration/cli/tests/wasm/example-longjmp.wasm index 6d7d8b48f64..ae48607bf74 100755 Binary files a/tests/integration/cli/tests/wasm/example-longjmp.wasm and b/tests/integration/cli/tests/wasm/example-longjmp.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-multi-threading.wasm b/tests/integration/cli/tests/wasm/example-multi-threading.wasm index 936d5bbe0e0..e0c82b10b46 100644 Binary files a/tests/integration/cli/tests/wasm/example-multi-threading.wasm and b/tests/integration/cli/tests/wasm/example-multi-threading.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-pipe.wasm b/tests/integration/cli/tests/wasm/example-pipe.wasm index 14af9d99966..1f94e454b5d 100644 Binary files a/tests/integration/cli/tests/wasm/example-pipe.wasm and b/tests/integration/cli/tests/wasm/example-pipe.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-sleep.wasm b/tests/integration/cli/tests/wasm/example-sleep.wasm index 3a93942664a..769f8f43a64 100644 Binary files a/tests/integration/cli/tests/wasm/example-sleep.wasm and b/tests/integration/cli/tests/wasm/example-sleep.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-spawn.wasm b/tests/integration/cli/tests/wasm/example-spawn.wasm index 16b344b19d1..46901831f13 100644 Binary files a/tests/integration/cli/tests/wasm/example-spawn.wasm and b/tests/integration/cli/tests/wasm/example-spawn.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-tokio.wasm b/tests/integration/cli/tests/wasm/example-tokio.wasm index a834c68b1ff..77b1a653bf9 100644 Binary files a/tests/integration/cli/tests/wasm/example-tokio.wasm and b/tests/integration/cli/tests/wasm/example-tokio.wasm differ diff --git a/tests/integration/cli/tests/wasm/example-vfork.wasm b/tests/integration/cli/tests/wasm/example-vfork.wasm index 476ad33f57e..7818145cdd3 100644 Binary files a/tests/integration/cli/tests/wasm/example-vfork.wasm and b/tests/integration/cli/tests/wasm/example-vfork.wasm differ diff --git a/tests/integration/cli/tests/wasm/threaded-memory.wasm b/tests/integration/cli/tests/wasm/threaded-memory.wasm index 85a56590ed1..2d636fe7eb8 100644 Binary files a/tests/integration/cli/tests/wasm/threaded-memory.wasm and b/tests/integration/cli/tests/wasm/threaded-memory.wasm differ diff --git a/tests/integration/cli/tests/wasm/web-server.wasm b/tests/integration/cli/tests/wasm/web-server.wasm index 640a3f48c9e..854b580c5de 100755 Binary files a/tests/integration/cli/tests/wasm/web-server.wasm and b/tests/integration/cli/tests/wasm/web-server.wasm differ diff --git a/tests/integration/cli/tests/webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc b/tests/integration/cli/tests/webc/static-web-server-1.0.92-22ccedaa-3f96-4de0-b24a-ef48ade8151b.webc similarity index 64% rename from tests/integration/cli/tests/webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc rename to tests/integration/cli/tests/webc/static-web-server-1.0.92-22ccedaa-3f96-4de0-b24a-ef48ade8151b.webc index 5c839c1f42a..b149d7e395a 100644 Binary files a/tests/integration/cli/tests/webc/static-web-server-1.0.8-a241658c-e409-4749-872c-ae8eab142ef0.webc and b/tests/integration/cli/tests/webc/static-web-server-1.0.92-22ccedaa-3f96-4de0-b24a-ef48ade8151b.webc differ