From 206212585029bdc688c0d0a63f7e5ff3201acd74 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Wed, 3 May 2023 14:56:15 +0800 Subject: [PATCH 01/18] Created a set of tiered module caches --- lib/wasi/src/runtime/mod.rs | 1 + lib/wasi/src/runtime/module_cache/and_then.rs | 91 ++++++++ lib/wasi/src/runtime/module_cache/mod.rs | 24 ++ lib/wasi/src/runtime/module_cache/on_disk.rs | 214 ++++++++++++++++++ lib/wasi/src/runtime/module_cache/shared.rs | 55 +++++ .../src/runtime/module_cache/thread_local.rs | 50 ++++ lib/wasi/src/runtime/module_cache/types.rs | 97 ++++++++ lib/wasi/src/runtime/resolver/types.rs | 12 +- 8 files changed, 541 insertions(+), 3 deletions(-) create mode 100644 lib/wasi/src/runtime/module_cache/and_then.rs create mode 100644 lib/wasi/src/runtime/module_cache/mod.rs create mode 100644 lib/wasi/src/runtime/module_cache/on_disk.rs create mode 100644 lib/wasi/src/runtime/module_cache/shared.rs create mode 100644 lib/wasi/src/runtime/module_cache/thread_local.rs create mode 100644 lib/wasi/src/runtime/module_cache/types.rs diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index 25c2b054f6a..1cae0088310 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -1,3 +1,4 @@ +pub mod module_cache; pub mod resolver; pub mod task_manager; diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs new file mode 100644 index 00000000000..a4bacf86438 --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -0,0 +1,91 @@ +use wasmer::{Engine, Module}; + +use crate::{ + runtime::module_cache::{CacheError, CompiledModuleCache}, + VirtualTaskManager, +}; + +/// A [`CompiledModuleCache`] combinator which will try operations on one cache +/// and fall back to a secondary cache if they fail. +/// +/// Constructed via [`CompiledModuleCache::and_then()`]. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AndThen { + primary: Primary, + secondary: Secondary, +} + +impl AndThen { + pub(crate) fn new(primary: Primary, secondary: Secondary) -> Self { + AndThen { primary, secondary } + } + + pub fn primary(&self) -> &Primary { + &self.primary + } + + pub fn primary_mut(&mut self) -> &mut Primary { + &mut self.primary + } + + pub fn secondary(&self) -> &Secondary { + &self.secondary + } + + pub fn secondary_mut(&mut self) -> &mut Secondary { + &mut self.secondary + } + + pub fn into_inner(self) -> (Primary, Secondary) { + let AndThen { primary, secondary } = self; + (primary, secondary) + } +} + +#[async_trait::async_trait] +impl CompiledModuleCache for AndThen +where + Primary: CompiledModuleCache + Send + Sync, + Secondary: CompiledModuleCache + Send + Sync, +{ + async fn load( + &self, + key: &str, + engine: &Engine, + task_manager: &dyn VirtualTaskManager, + ) -> Result { + let primary_error = match self.primary.load(key, engine, task_manager).await { + Ok(m) => return Ok(m), + Err(e) => e, + }; + + if let Ok(m) = self.secondary.load(key, engine, task_manager).await { + // Now we've got a module, let's make sure it ends up in the primary + // cache too. + if let Err(e) = self.primary.save(key, &m, task_manager).await { + tracing::warn!( + key, + error = &e as &dyn std::error::Error, + "Unable to save a module to the primary cache", + ); + } + + return Ok(m); + } + + Err(primary_error) + } + + async fn save( + &self, + key: &str, + module: &Module, + task_manager: &dyn VirtualTaskManager, + ) -> Result<(), CacheError> { + futures::try_join!( + self.primary.save(key, module, task_manager), + self.secondary.save(key, module, task_manager) + )?; + Ok(()) + } +} diff --git a/lib/wasi/src/runtime/module_cache/mod.rs b/lib/wasi/src/runtime/module_cache/mod.rs new file mode 100644 index 00000000000..915c68ff1f0 --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/mod.rs @@ -0,0 +1,24 @@ +mod and_then; +mod on_disk; +mod shared; +mod thread_local; +mod types; + +pub use self::{ + and_then::AndThen, + on_disk::OnDiskCache, + shared::SharedCache, + thread_local::ThreadLocalCache, + types::{CacheError, CompiledModuleCache}, +}; + +/// Get a [`CompiledModuleCache`] which should be good enough for most use +/// cases. +/// +/// The returned object will use a mix of in-memory and on-disk caching +/// strategies. +pub fn default_cache(cache_dir: &std::path::Path) -> impl CompiledModuleCache { + ThreadLocalCache::default() + .and_then(SharedCache::default()) + .and_then(OnDiskCache::new(cache_dir)) +} diff --git a/lib/wasi/src/runtime/module_cache/on_disk.rs b/lib/wasi/src/runtime/module_cache/on_disk.rs new file mode 100644 index 00000000000..f68445d66f2 --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/on_disk.rs @@ -0,0 +1,214 @@ +use std::path::{Path, PathBuf}; + +use wasmer::{Engine, Module}; + +use crate::runtime::{ + module_cache::{CacheError, CompiledModuleCache}, + VirtualTaskManager, +}; + +/// A cache that saves modules to a folder on disk using +/// [`Module::serialize()`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct OnDiskCache { + cache_dir: PathBuf, +} + +impl OnDiskCache { + pub fn new(cache_dir: impl Into) -> Self { + OnDiskCache { + cache_dir: cache_dir.into(), + } + } + + pub fn cache_dir(&self) -> &Path { + &self.cache_dir + } + + fn path(&self, key: &str) -> PathBuf { + let illegal_path_characters = ['/', '\\', ':', '.']; + let sanitized_key = key.replace(illegal_path_characters, "_"); + + self.cache_dir.join(sanitized_key).with_extension("bin") + } +} + +#[async_trait::async_trait] +impl CompiledModuleCache for OnDiskCache { + async fn load( + &self, + key: &str, + engine: &Engine, + task_manager: &dyn VirtualTaskManager, + ) -> Result { + let path = self.path(key); + + // Note: engine is reference-counted so it's cheap to clone + let engine = engine.clone(); + + let result = task_manager + .runtime() + .spawn_blocking(move || { + Module::deserialize_from_file_checked(&engine, &path) + .map_err(|e| deserialize_error(e, path)) + }) + .await; + + propagate_panic(result) + } + + async fn save( + &self, + key: &str, + module: &Module, + task_manager: &dyn VirtualTaskManager, + ) -> Result<(), CacheError> { + let path = self.path(key); + + // Note: module is reference-counted so it's cheap to clone + let module = module.clone(); + + let result = task_manager + .runtime() + .spawn_blocking(move || { + if let Some(parent) = path.parent() { + if let Err(e) = std::fs::create_dir_all(parent) { + tracing::warn!( + dir=%parent.display(), + error=&e as &dyn std::error::Error, + "Unable to create the cache dir", + ); + } + } + + // PERF: We can reduce disk usage by using the weezl crate to + // LZW-encode the serialized module. + module + .serialize_to_file(&path) + .map_err(|e| CacheError::Other(Box::new(SerializeError { path, inner: e }))) + }) + .await; + + propagate_panic(result) + } +} + +/// Resume any panics that may have occurred inside a spawned task. +fn propagate_panic( + result: Result, tokio::task::JoinError>, +) -> Result { + match result { + Ok(ret) => ret, + Err(e) => match e.try_into_panic() { + Ok(payload) => std::panic::resume_unwind(payload), + Err(e) => Err(CacheError::Other(e.into())), + }, + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Unable to save to \"{}\"", path.display())] +struct SerializeError { + path: PathBuf, + #[source] + inner: wasmer::SerializeError, +} + +#[derive(Debug, thiserror::Error)] +#[error("Unable to deserialize from \"{}\"", path.display())] +struct DeserializeError { + path: PathBuf, + #[source] + inner: wasmer::DeserializeError, +} + +fn deserialize_error(e: wasmer::DeserializeError, path: PathBuf) -> CacheError { + match e { + wasmer::DeserializeError::Io(io) if io.kind() == std::io::ErrorKind::NotFound => { + CacheError::NotFound + } + other => CacheError::Other(Box::new(DeserializeError { path, inner: other })), + } +} + +#[cfg(test)] +mod tests { + use tempfile::TempDir; + + use super::*; + use crate::runtime::task_manager::tokio::TokioTaskManager; + + const ADD_WAT: &[u8] = br#"( + module + (func + (export "add") + (param $x i64) + (param $y i64) + (result i64) + (i64.add (local.get $x) (local.get $y))) + )"#; + + #[tokio::test] + async fn save_to_disk() { + let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let cache = OnDiskCache::new(temp.path()); + let key = "wat"; + + cache.save(key, &module, &task_manager).await.unwrap(); + + assert!(temp.path().join(key).with_extension("bin").exists()); + } + + #[tokio::test] + async fn create_cache_dir_automatically() { + let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let cache_dir = temp.path().join("this").join("doesn't").join("exist"); + assert!(!cache_dir.exists()); + let cache = OnDiskCache::new(&cache_dir); + let key = "wat"; + + cache.save(key, &module, &task_manager).await.unwrap(); + + assert!(cache_dir.is_dir()); + } + + #[tokio::test] + async fn missing_file() { + let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let key = "wat"; + let cache = OnDiskCache::new(temp.path()); + + let err = cache.load(key, &engine, &task_manager).await.unwrap_err(); + + assert!(matches!(err, CacheError::NotFound)); + } + + #[tokio::test] + async fn load_from_disk() { + let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let key = "wat"; + module + .serialize_to_file(temp.path().join(key).with_extension("bin")) + .unwrap(); + let cache = OnDiskCache::new(temp.path()); + + let module = cache.load(key, &engine, &task_manager).await.unwrap(); + + let exports: Vec<_> = module + .exports() + .map(|export| export.name().to_string()) + .collect(); + assert_eq!(exports, ["add"]); + } +} diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs new file mode 100644 index 00000000000..cf73c6e699c --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -0,0 +1,55 @@ +use std::{collections::HashMap, sync::Arc}; + +use tokio::sync::RwLock; +use wasmer::{Engine, Module}; + +use crate::{ + runtime::module_cache::{CacheError, CompiledModuleCache}, + VirtualTaskManager, +}; + +/// A [`CompiledModuleCache`] based on a +/// [Arc]<[RwLock]<[HashMap]<[String], [Module]>>> that can be +/// shared. +#[derive(Debug, Default, Clone)] +pub struct SharedCache { + modules: Arc>>, +} + +impl SharedCache { + pub fn new() -> SharedCache { + SharedCache::default() + } + + pub fn from_existing_modules(modules: Arc>>) -> Self { + SharedCache { modules } + } +} + +#[async_trait::async_trait] +impl CompiledModuleCache for SharedCache { + async fn load( + &self, + key: &str, + _engine: &Engine, + _task_manager: &dyn VirtualTaskManager, + ) -> Result { + let modules = self.modules.read().await; + + modules.get(key).cloned().ok_or(CacheError::NotFound) + } + + async fn save( + &self, + key: &str, + module: &Module, + _task_manager: &dyn VirtualTaskManager, + ) -> Result<(), CacheError> { + let module = module.clone(); + let key = key.to_string(); + + self.modules.write().await.insert(key, module); + + Ok(()) + } +} diff --git a/lib/wasi/src/runtime/module_cache/thread_local.rs b/lib/wasi/src/runtime/module_cache/thread_local.rs new file mode 100644 index 00000000000..e93a565b059 --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/thread_local.rs @@ -0,0 +1,50 @@ +use std::{cell::RefCell, collections::HashMap}; + +use wasmer::{Engine, Module}; + +use crate::runtime::{ + module_cache::{CacheError, CompiledModuleCache}, + VirtualTaskManager, +}; + +std::thread_local! { + static CACHED_MODULES: RefCell> + = RefCell::new(HashMap::new()); +} + +/// A cache that will cache modules in a thread-local variable. +#[derive(Debug, Default)] +#[non_exhaustive] +pub struct ThreadLocalCache {} + +impl ThreadLocalCache { + fn lookup(&self, key: &str) -> Option { + CACHED_MODULES.with(|m| m.borrow().get(key).cloned()) + } + + fn insert(&self, key: &str, module: &Module) { + CACHED_MODULES.with(|m| m.borrow_mut().insert(key.to_string(), module.clone())); + } +} + +#[async_trait::async_trait] +impl CompiledModuleCache for ThreadLocalCache { + async fn load( + &self, + key: &str, + _engine: &Engine, + _task_manager: &dyn VirtualTaskManager, + ) -> Result { + self.lookup(key).ok_or(CacheError::NotFound) + } + + async fn save( + &self, + key: &str, + module: &Module, + _task_manager: &dyn VirtualTaskManager, + ) -> Result<(), CacheError> { + self.insert(key, module); + Ok(()) + } +} diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs new file mode 100644 index 00000000000..0d4e2ec931e --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -0,0 +1,97 @@ +use std::{fmt::Debug, ops::Deref}; + +use wasmer::{Engine, Module}; + +use crate::runtime::{module_cache::AndThen, VirtualTaskManager}; + +/// A cache for compiled WebAssembly modules. +/// +/// ## Assumptions +/// +/// Implementations can assume that cache keys are unique and that using the +/// same key to load or save will always result in the "same" module. +/// +/// Implementations can also assume that [`CompiledModuleCache::load()`] will +/// be called more often than [`CompiledModuleCache::save()`] and optimise +/// their caching strategy accordingly. +#[async_trait::async_trait] +pub trait CompiledModuleCache: Debug + Send + Sync { + async fn load( + &self, + key: &str, + engine: &Engine, + task_manager: &dyn VirtualTaskManager, + ) -> Result; + + async fn save( + &self, + key: &str, + module: &Module, + task_manager: &dyn VirtualTaskManager, + ) -> Result<(), CacheError>; + + /// Chain a second cache onto this one. + /// + /// The general assumption is that each subsequent cache in the chain will + /// be significantly slower than the previous one. + /// + /// ```rust + /// use wasmer_wasix::runtime::module_cache::{ + /// CompiledModuleCache, ThreadLocalCache, OnDiskCache, SharedCache, + /// }; + /// + /// let cache = ThreadLocalCache::default() + /// .and_then(SharedCache::default()) + /// .and_then(OnDiskCache::new("~/.local/cache")); + /// ``` + fn and_then(self, other: C) -> AndThen + where + Self: Sized, + C: CompiledModuleCache, + { + AndThen::new(self, other) + } +} + +#[async_trait::async_trait] +impl CompiledModuleCache for D +where + D: Deref + Debug + Send + Sync, + C: CompiledModuleCache + ?Sized, +{ + async fn load( + &self, + key: &str, + engine: &Engine, + task_manager: &dyn VirtualTaskManager, + ) -> Result { + (**self).load(key, engine, task_manager).await + } + async fn save( + &self, + key: &str, + module: &Module, + task_manager: &dyn VirtualTaskManager, + ) -> Result<(), CacheError> { + (**self).save(key, module, task_manager).await + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CacheError { + /// The item was not found. + #[error("Not found")] + NotFound, + #[error(transparent)] + Other(Box), +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_object_safe() { + let _: Option> = None; + } +} diff --git a/lib/wasi/src/runtime/resolver/types.rs b/lib/wasi/src/runtime/resolver/types.rs index 3058494dcdd..566c43656a2 100644 --- a/lib/wasi/src/runtime/resolver/types.rs +++ b/lib/wasi/src/runtime/resolver/types.rs @@ -1,4 +1,10 @@ -use std::{collections::BTreeMap, fmt::Display, ops::Deref, path::PathBuf, str::FromStr}; +use std::{ + collections::BTreeMap, + fmt::{Debug, Display}, + ops::Deref, + path::PathBuf, + str::FromStr, +}; use anyhow::Context; use semver::VersionReq; @@ -6,7 +12,7 @@ use semver::VersionReq; use crate::{bin_factory::BinaryPackage, http::HttpClient, runtime::resolver::InMemoryCache}; #[async_trait::async_trait] -pub trait PackageResolver: std::fmt::Debug + Send + Sync { +pub trait PackageResolver: Debug + Send + Sync { /// Resolve a package, loading all dependencies. async fn resolve_package( &self, @@ -26,7 +32,7 @@ pub trait PackageResolver: std::fmt::Debug + Send + Sync { #[async_trait::async_trait] impl PackageResolver for D where - D: Deref + std::fmt::Debug + Send + Sync, + D: Deref + Debug + Send + Sync, R: PackageResolver + ?Sized, { /// Resolve a package, loading all dependencies. From f4beab18c3540e64ad7ffdad3abfff5a16d6bc97 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Wed, 3 May 2023 15:17:50 +0800 Subject: [PATCH 02/18] Switched ModuleCache over to the new CompiledModuleCache infrastructure --- lib/wasi/src/bin_factory/exec.rs | 18 +++- lib/wasi/src/bin_factory/module_cache.rs | 130 +++++------------------ lib/wasi/src/runtime/task_manager/mod.rs | 74 +++++++++++-- 3 files changed, 107 insertions(+), 115 deletions(-) diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index 62a4becdacd..ff601dad830 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -26,7 +26,15 @@ pub fn spawn_exec( // The deterministic id for this engine let compiler = store.engine().deterministic_id(); - let module = compiled_modules.get_compiled_module(&store, binary.hash().as_str(), compiler); + let tasks = runtime.task_manager(); + + let module = tasks.block_on(compiled_modules.get_compiled_module( + &store, + binary.hash().as_str(), + compiler, + &**tasks, + )); + let module = match (module, binary.entry.as_ref()) { (Some(a), _) => a, (None, Some(entry)) => { @@ -43,7 +51,13 @@ pub fn spawn_exec( env.blocking_cleanup(Some(Errno::Noexec.into())); } let module = module?; - compiled_modules.set_compiled_module(binary.hash().as_str(), compiler, &module); + + tasks.block_on(compiled_modules.set_compiled_module( + binary.hash().as_str(), + compiler, + &module, + &**tasks, + )); module } (None, None) => { diff --git a/lib/wasi/src/bin_factory/module_cache.rs b/lib/wasi/src/bin_factory/module_cache.rs index e14fc29d68c..9f98a1a79c6 100644 --- a/lib/wasi/src/bin_factory/module_cache.rs +++ b/lib/wasi/src/bin_factory/module_cache.rs @@ -1,18 +1,15 @@ -use std::{cell::RefCell, collections::HashMap, ops::DerefMut, path::PathBuf, sync::RwLock}; +use std::path::PathBuf; use anyhow::Context; use wasmer::Module; use super::BinaryPackage; -use crate::WasiRuntime; +use crate::{runtime::module_cache::CompiledModuleCache, VirtualTaskManager, WasiRuntime}; pub const DEFAULT_COMPILED_PATH: &str = "~/.wasmer/compiled"; #[derive(Debug)] -pub struct ModuleCache { - pub(crate) cache_compile_dir: PathBuf, - pub(crate) cached_modules: Option>>, -} +pub struct ModuleCache(Box); // FIXME: remove impls! // Added as a stopgap to get the crate to compile again with the "js" feature. @@ -28,31 +25,18 @@ impl Default for ModuleCache { } } -thread_local! { - static THREAD_LOCAL_CACHED_MODULES: std::cell::RefCell> - = RefCell::new(HashMap::new()); -} - impl ModuleCache { /// Create a new [`ModuleCache`]. /// /// use_shared_cache enables a shared cache of modules in addition to a thread-local cache. - pub fn new(cache_compile_dir: Option, use_shared_cache: bool) -> ModuleCache { + pub fn new(cache_compile_dir: Option, _use_shared_cache: bool) -> ModuleCache { let cache_compile_dir = cache_compile_dir.unwrap_or_else(|| { PathBuf::from(shellexpand::tilde(DEFAULT_COMPILED_PATH).into_owned()) }); - let _ = std::fs::create_dir_all(&cache_compile_dir); - let cached_modules = if use_shared_cache { - Some(RwLock::new(HashMap::default())) - } else { - None - }; + let cache = crate::runtime::module_cache::default_cache(&cache_compile_dir); - ModuleCache { - cached_modules, - cache_compile_dir, - } + ModuleCache(Box::new(cache)) } pub fn get_webc( @@ -75,99 +59,37 @@ impl ModuleCache { }) } - pub fn get_compiled_module( + pub async fn get_compiled_module( &self, engine: &impl wasmer::AsEngineRef, data_hash: &str, compiler: &str, + task_manager: &dyn VirtualTaskManager, ) -> Option { let key = format!("{}-{}", data_hash, compiler); - // fastest path - { - let module = THREAD_LOCAL_CACHED_MODULES.with(|cache| { - let cache = cache.borrow(); - cache.get(&key).cloned() - }); - if let Some(module) = module { - return Some(module); - } - } - - // fast path - if let Some(cache) = &self.cached_modules { - let cache = cache.read().unwrap(); - if let Some(module) = cache.get(&key) { - THREAD_LOCAL_CACHED_MODULES.with(|cache| { - let mut cache = cache.borrow_mut(); - cache.insert(key.clone(), module.clone()); - }); - return Some(module.clone()); - } - } - - // slow path - let path = self.cache_compile_dir.join(format!("{}.bin", key).as_str()); - if let Ok(data) = std::fs::read(path.as_path()) { - tracing::trace!("bin file found: {:?} [len={}]", path.as_path(), data.len()); - let mut decoder = weezl::decode::Decoder::new(weezl::BitOrder::Msb, 8); - if let Ok(data) = decoder.decode(&data[..]) { - let module_bytes = bytes::Bytes::from(data); - - // Load the module - let module = match Module::deserialize_checked(engine, &module_bytes[..]) { - Ok(m) => m, - Err(err) => { - tracing::error!( - "failed to deserialize module with hash '{data_hash}': {err}" - ); - return None; - } - }; - - if let Some(cache) = &self.cached_modules { - let mut cache = cache.write().unwrap(); - cache.insert(key.clone(), module.clone()); - } - - THREAD_LOCAL_CACHED_MODULES.with(|cache| { - let mut cache = cache.borrow_mut(); - cache.insert(key.clone(), module.clone()); - }); - return Some(module); - } - } - - // Not found - tracing::trace!("bin file not found: {:?}", path); - None + self.0 + .load(&key, engine.as_engine_ref().engine(), task_manager) + .await + .ok() } - pub fn set_compiled_module(&self, data_hash: &str, compiler: &str, module: &Module) { + pub async fn set_compiled_module( + &self, + data_hash: &str, + compiler: &str, + module: &Module, + task_manager: &dyn VirtualTaskManager, + ) { let key = format!("{}-{}", data_hash, compiler); - // Add the module to the local thread cache - THREAD_LOCAL_CACHED_MODULES.with(|cache| { - let mut cache = cache.borrow_mut(); - let cache = cache.deref_mut(); - cache.insert(key.clone(), module.clone()); - }); - - // Serialize the compiled module into bytes and insert it into the cache - if let Some(cache) = &self.cached_modules { - let mut cache = cache.write().unwrap(); - cache.insert(key.clone(), module.clone()); - } - - // We should also attempt to store it in the cache directory - let compiled_bytes = module.serialize().unwrap(); - - let path = self.cache_compile_dir.join(format!("{}.bin", key).as_str()); - // TODO: forward error! - let _ = std::fs::create_dir_all(path.parent().unwrap()); - let mut encoder = weezl::encode::Encoder::new(weezl::BitOrder::Msb, 8); - if let Ok(compiled_bytes) = encoder.encode(&compiled_bytes[..]) { - let _ = std::fs::write(path, &compiled_bytes[..]); + if let Err(e) = self.0.save(&key, module, task_manager).await { + tracing::warn!( + data_hash, + compiler, + error = &e as &dyn std::error::Error, + "Unable to cache the module", + ); } } } diff --git a/lib/wasi/src/runtime/task_manager/mod.rs b/lib/wasi/src/runtime/task_manager/mod.rs index 7116d27d55e..1e3f7bbd34d 100644 --- a/lib/wasi/src/runtime/task_manager/mod.rs +++ b/lib/wasi/src/runtime/task_manager/mod.rs @@ -2,7 +2,7 @@ #[cfg(feature = "sys-thread")] pub mod tokio; -use std::{pin::Pin, time::Duration}; +use std::{ops::Deref, pin::Pin, time::Duration}; use ::tokio::runtime::Handle; use futures::Future; @@ -79,6 +79,64 @@ 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, + T: VirtualTaskManager + ?Sized, +{ + fn build_memory( + &self, + store: &mut StoreMut, + spawn_type: SpawnType, + ) -> Result, WasiThreadError> { + (**self).build_memory(store, spawn_type) + } + + async fn sleep_now(&self, time: Duration) { + (**self).sleep_now(time).await + } + + fn task_shared( + &self, + task: Box< + dyn FnOnce() -> Pin + Send + 'static>> + Send + 'static, + >, + ) -> Result<(), WasiThreadError> { + (**self).task_shared(task) + } + + fn runtime(&self) -> &Handle { + (**self).runtime() + } + + #[allow(dyn_drop)] + fn runtime_enter<'g>(&'g self) -> Box { + (**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_dedicated( + &self, + task: Box, + ) -> Result<(), WasiThreadError> { + (**self).task_dedicated(task) + } + + fn thread_parallelism(&self) -> Result { + (**self).thread_parallelism() + } +} + impl dyn VirtualTaskManager { /// Execute a future and return the output. /// This method blocks until the future is complete. @@ -93,14 +151,12 @@ pub trait VirtualTaskManagerExt { fn block_on<'a, A>(&self, task: impl Future + 'a) -> A; } -impl<'a, T: VirtualTaskManager> VirtualTaskManagerExt for &'a T { - fn block_on<'x, A>(&self, task: impl Future + 'x) -> A { - self.runtime().block_on(task) - } -} - -impl VirtualTaskManagerExt for std::sync::Arc { - fn block_on<'x, A>(&self, task: impl Future + 'x) -> A { +impl VirtualTaskManagerExt for D +where + D: Deref, + T: VirtualTaskManager + ?Sized, +{ + fn block_on<'a, A>(&self, task: impl Future + 'a) -> A { self.runtime().block_on(task) } } From a8864aaa495b19055b8cbdfc7267f1cdf575925c Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Wed, 3 May 2023 15:39:10 +0800 Subject: [PATCH 03/18] Made ModuleCache sync again --- lib/wasi/src/bin_factory/exec.rs | 15 ++++----------- lib/wasi/src/bin_factory/module_cache.rs | 23 +++++++++++++---------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index ff601dad830..073e75e3b6d 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -26,14 +26,7 @@ pub fn spawn_exec( // The deterministic id for this engine let compiler = store.engine().deterministic_id(); - let tasks = runtime.task_manager(); - - let module = tasks.block_on(compiled_modules.get_compiled_module( - &store, - binary.hash().as_str(), - compiler, - &**tasks, - )); + let module = compiled_modules.get_compiled_module(&**runtime, binary.hash().as_str(), compiler); let module = match (module, binary.entry.as_ref()) { (Some(a), _) => a, @@ -52,12 +45,12 @@ pub fn spawn_exec( } let module = module?; - tasks.block_on(compiled_modules.set_compiled_module( + compiled_modules.set_compiled_module( + &**runtime, binary.hash().as_str(), compiler, &module, - &**tasks, - )); + ); module } (None, None) => { diff --git a/lib/wasi/src/bin_factory/module_cache.rs b/lib/wasi/src/bin_factory/module_cache.rs index 9f98a1a79c6..a90dfd511a0 100644 --- a/lib/wasi/src/bin_factory/module_cache.rs +++ b/lib/wasi/src/bin_factory/module_cache.rs @@ -4,7 +4,7 @@ use anyhow::Context; use wasmer::Module; use super::BinaryPackage; -use crate::{runtime::module_cache::CompiledModuleCache, VirtualTaskManager, WasiRuntime}; +use crate::{runtime::module_cache::CompiledModuleCache, WasiRuntime}; pub const DEFAULT_COMPILED_PATH: &str = "~/.wasmer/compiled"; @@ -59,31 +59,34 @@ impl ModuleCache { }) } - pub async fn get_compiled_module( + pub fn get_compiled_module( &self, - engine: &impl wasmer::AsEngineRef, + runtime: &dyn WasiRuntime, data_hash: &str, compiler: &str, - task_manager: &dyn VirtualTaskManager, ) -> Option { let key = format!("{}-{}", data_hash, compiler); + let engine = runtime.engine()?; - self.0 - .load(&key, engine.as_engine_ref().engine(), task_manager) - .await + let tasks = runtime.task_manager(); + tasks + .block_on(async { self.0.load(&key, &engine, tasks).await }) .ok() } - pub async fn set_compiled_module( + pub fn set_compiled_module( &self, + runtime: &dyn WasiRuntime, data_hash: &str, compiler: &str, module: &Module, - task_manager: &dyn VirtualTaskManager, ) { let key = format!("{}-{}", data_hash, compiler); - if let Err(e) = self.0.save(&key, module, task_manager).await { + let tasks = runtime.task_manager(); + let result = tasks.block_on(async { self.0.save(&key, module, tasks).await }); + + if let Err(e) = result { tracing::warn!( data_hash, compiler, From 3ed1192bfaa9504a3543dff1958c66462693b769 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Thu, 4 May 2023 20:09:55 +0800 Subject: [PATCH 04/18] Make spawn_exec() asynchronous --- lib/wasi/src/bin_factory/exec.rs | 17 ++++++++-------- lib/wasi/src/bin_factory/module_cache.rs | 10 ++++------ .../src/os/command/builtins/cmd_wasmer.rs | 4 +++- lib/wasi/src/os/console/mod.rs | 4 ++-- lib/wasi/src/syscalls/wasix/proc_exec.rs | 20 +++++++++---------- 5 files changed, 27 insertions(+), 28 deletions(-) diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index 073e75e3b6d..ffde2fd4557 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -15,7 +15,8 @@ use crate::{ WasiFunctionEnv, WasiRuntime, }; -pub fn spawn_exec( +#[tracing::instrument(level = "trace", skip_all, fields(%name, %binary.package_name))] +pub async fn spawn_exec( binary: BinaryPackage, name: &str, store: Store, @@ -26,7 +27,9 @@ pub fn spawn_exec( // The deterministic id for this engine let compiler = store.engine().deterministic_id(); - let module = compiled_modules.get_compiled_module(&**runtime, binary.hash().as_str(), compiler); + let module = compiled_modules + .get_compiled_module(&**runtime, binary.hash().as_str(), compiler) + .await; let module = match (module, binary.entry.as_ref()) { (Some(a), _) => a, @@ -45,12 +48,9 @@ pub fn spawn_exec( } let module = module?; - compiled_modules.set_compiled_module( - &**runtime, - binary.hash().as_str(), - compiler, - &module, - ); + compiled_modules + .set_compiled_module(&**runtime, binary.hash().as_str(), compiler, &module) + .await; module } (None, None) => { @@ -220,6 +220,7 @@ impl BinFactory { &self.runtime, &self.cache, ) + .await }) } diff --git a/lib/wasi/src/bin_factory/module_cache.rs b/lib/wasi/src/bin_factory/module_cache.rs index a90dfd511a0..1286cebd157 100644 --- a/lib/wasi/src/bin_factory/module_cache.rs +++ b/lib/wasi/src/bin_factory/module_cache.rs @@ -59,7 +59,7 @@ impl ModuleCache { }) } - pub fn get_compiled_module( + pub async fn get_compiled_module( &self, runtime: &dyn WasiRuntime, data_hash: &str, @@ -69,12 +69,10 @@ impl ModuleCache { let engine = runtime.engine()?; let tasks = runtime.task_manager(); - tasks - .block_on(async { self.0.load(&key, &engine, tasks).await }) - .ok() + self.0.load(&key, &engine, tasks).await.ok() } - pub fn set_compiled_module( + pub async fn set_compiled_module( &self, runtime: &dyn WasiRuntime, data_hash: &str, @@ -84,7 +82,7 @@ impl ModuleCache { let key = format!("{}-{}", data_hash, compiler); let tasks = runtime.task_manager(); - let result = tasks.block_on(async { self.0.save(&key, module, tasks).await }); + let result = self.0.save(&key, module, tasks).await; if let Err(e) = result { tracing::warn!( diff --git a/lib/wasi/src/os/command/builtins/cmd_wasmer.rs b/lib/wasi/src/os/command/builtins/cmd_wasmer.rs index edbb4471a8f..39fc9be89b9 100644 --- a/lib/wasi/src/os/command/builtins/cmd_wasmer.rs +++ b/lib/wasi/src/os/command/builtins/cmd_wasmer.rs @@ -81,7 +81,9 @@ impl CmdWasmer { // Get the binary if let Some(binary) = self.get_package(what.clone()) { // Now run the module - spawn_exec(binary, name, store, env, &self.runtime, &self.cache) + parent_ctx.data().tasks().block_on(async { + spawn_exec(binary, name, store, env, &self.runtime, &self.cache).await + }) } else { parent_ctx.data().tasks().block_on(async move { let _ = stderr_write( diff --git a/lib/wasi/src/os/console/mod.rs b/lib/wasi/src/os/console/mod.rs index 201d3c9e6e5..a758230f1f3 100644 --- a/lib/wasi/src/os/console/mod.rs +++ b/lib/wasi/src/os/console/mod.rs @@ -242,14 +242,14 @@ impl Console { // Build the config // Run the binary - let process = spawn_exec( + let process = tasks.block_on(spawn_exec( binary, prog, store, env, &self.runtime, self.compiled_modules.as_ref(), - )?; + ))?; // Return the process Ok((process, wasi_process)) diff --git a/lib/wasi/src/syscalls/wasix/proc_exec.rs b/lib/wasi/src/syscalls/wasix/proc_exec.rs index 6e1160f6c1e..218ccb3a860 100644 --- a/lib/wasi/src/syscalls/wasix/proc_exec.rs +++ b/lib/wasi/src/syscalls/wasix/proc_exec.rs @@ -101,18 +101,13 @@ pub fn proc_exec( let new_store = new_store.take().unwrap(); let env = config.take().unwrap(); - tasks.block_on(async { - let name_inner = name.clone(); - let ret = bin_factory.spawn( - name_inner, - new_store, - env, - ) - .await; + let name_inner = name.clone(); + __asyncify_light(ctx.data(), None, async { + let ret = bin_factory.spawn(name_inner, new_store, env).await; match ret { Ok(ret) => { trace!(%child_pid, "spawned sub-process"); - }, + } Err(err) => { err_exit_code = conv_bus_err_to_exit_code(err); @@ -127,10 +122,13 @@ pub fn proc_exec( &ctx, format!("wasm execute failed [{}] - {}\n", name.as_str(), err) .as_bytes(), - ).await; + ) + .await; } } - }) + + Ok(()) + }); } } }; From 09d5493b674153d2221907dd289aaf19a4e94707 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Thu, 4 May 2023 20:37:51 +0800 Subject: [PATCH 05/18] Removed the task manager from CompiledModuleCache so tests build on NodeJS --- lib/wasi/src/bin_factory/exec.rs | 2 +- lib/wasi/src/bin_factory/module_cache.rs | 14 +-- lib/wasi/src/runtime/module_cache/and_then.rs | 29 ++--- lib/wasi/src/runtime/module_cache/on_disk.rs | 100 +++++------------- lib/wasi/src/runtime/module_cache/shared.rs | 19 +--- .../src/runtime/module_cache/thread_local.rs | 19 +--- lib/wasi/src/runtime/module_cache/types.rs | 35 ++---- 7 files changed, 52 insertions(+), 166 deletions(-) diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index ffde2fd4557..0e626d7fbe5 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -49,7 +49,7 @@ pub async fn spawn_exec( let module = module?; compiled_modules - .set_compiled_module(&**runtime, binary.hash().as_str(), compiler, &module) + .set_compiled_module(binary.hash().as_str(), compiler, &module) .await; module } diff --git a/lib/wasi/src/bin_factory/module_cache.rs b/lib/wasi/src/bin_factory/module_cache.rs index 1286cebd157..da031ec1801 100644 --- a/lib/wasi/src/bin_factory/module_cache.rs +++ b/lib/wasi/src/bin_factory/module_cache.rs @@ -68,21 +68,13 @@ impl ModuleCache { let key = format!("{}-{}", data_hash, compiler); let engine = runtime.engine()?; - let tasks = runtime.task_manager(); - self.0.load(&key, &engine, tasks).await.ok() + self.0.load(&key, &engine).await.ok() } - pub async fn set_compiled_module( - &self, - runtime: &dyn WasiRuntime, - data_hash: &str, - compiler: &str, - module: &Module, - ) { + pub async fn set_compiled_module(&self, data_hash: &str, compiler: &str, module: &Module) { let key = format!("{}-{}", data_hash, compiler); - let tasks = runtime.task_manager(); - let result = self.0.save(&key, module, tasks).await; + let result = self.0.save(&key, module).await; if let Err(e) = result { tracing::warn!( diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs index a4bacf86438..6a40de4a8f0 100644 --- a/lib/wasi/src/runtime/module_cache/and_then.rs +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -1,9 +1,6 @@ use wasmer::{Engine, Module}; -use crate::{ - runtime::module_cache::{CacheError, CompiledModuleCache}, - VirtualTaskManager, -}; +use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; /// A [`CompiledModuleCache`] combinator which will try operations on one cache /// and fall back to a secondary cache if they fail. @@ -48,21 +45,16 @@ where Primary: CompiledModuleCache + Send + Sync, Secondary: CompiledModuleCache + Send + Sync, { - async fn load( - &self, - key: &str, - engine: &Engine, - task_manager: &dyn VirtualTaskManager, - ) -> Result { - let primary_error = match self.primary.load(key, engine, task_manager).await { + async fn load(&self, key: &str, engine: &Engine) -> Result { + let primary_error = match self.primary.load(key, engine).await { Ok(m) => return Ok(m), Err(e) => e, }; - if let Ok(m) = self.secondary.load(key, engine, task_manager).await { + if let Ok(m) = self.secondary.load(key, engine).await { // Now we've got a module, let's make sure it ends up in the primary // cache too. - if let Err(e) = self.primary.save(key, &m, task_manager).await { + if let Err(e) = self.primary.save(key, &m).await { tracing::warn!( key, error = &e as &dyn std::error::Error, @@ -76,15 +68,10 @@ where Err(primary_error) } - async fn save( - &self, - key: &str, - module: &Module, - task_manager: &dyn VirtualTaskManager, - ) -> Result<(), CacheError> { + async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { futures::try_join!( - self.primary.save(key, module, task_manager), - self.secondary.save(key, module, task_manager) + self.primary.save(key, module,), + self.secondary.save(key, module,) )?; Ok(()) } diff --git a/lib/wasi/src/runtime/module_cache/on_disk.rs b/lib/wasi/src/runtime/module_cache/on_disk.rs index f68445d66f2..8d6bb85edde 100644 --- a/lib/wasi/src/runtime/module_cache/on_disk.rs +++ b/lib/wasi/src/runtime/module_cache/on_disk.rs @@ -2,10 +2,7 @@ use std::path::{Path, PathBuf}; use wasmer::{Engine, Module}; -use crate::runtime::{ - module_cache::{CacheError, CompiledModuleCache}, - VirtualTaskManager, -}; +use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; /// A cache that saves modules to a folder on disk using /// [`Module::serialize()`]. @@ -35,74 +32,34 @@ impl OnDiskCache { #[async_trait::async_trait] impl CompiledModuleCache for OnDiskCache { - async fn load( - &self, - key: &str, - engine: &Engine, - task_manager: &dyn VirtualTaskManager, - ) -> Result { + async fn load(&self, key: &str, engine: &Engine) -> Result { let path = self.path(key); - // Note: engine is reference-counted so it's cheap to clone - let engine = engine.clone(); - - let result = task_manager - .runtime() - .spawn_blocking(move || { - Module::deserialize_from_file_checked(&engine, &path) - .map_err(|e| deserialize_error(e, path)) - }) - .await; - - propagate_panic(result) + // FIXME: Use spawn_blocking() to avoid blocking the thread + Module::deserialize_from_file_checked(&engine, &path) + .map_err(|e| deserialize_error(e, path)) } - async fn save( - &self, - key: &str, - module: &Module, - task_manager: &dyn VirtualTaskManager, - ) -> Result<(), CacheError> { + async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { let path = self.path(key); - // Note: module is reference-counted so it's cheap to clone - let module = module.clone(); - - let result = task_manager - .runtime() - .spawn_blocking(move || { - if let Some(parent) = path.parent() { - if let Err(e) = std::fs::create_dir_all(parent) { - tracing::warn!( - dir=%parent.display(), - error=&e as &dyn std::error::Error, - "Unable to create the cache dir", - ); - } - } - - // PERF: We can reduce disk usage by using the weezl crate to - // LZW-encode the serialized module. - module - .serialize_to_file(&path) - .map_err(|e| CacheError::Other(Box::new(SerializeError { path, inner: e }))) - }) - .await; - - propagate_panic(result) - } -} + // FIXME: Use spawn_blocking() to avoid blocking the thread -/// Resume any panics that may have occurred inside a spawned task. -fn propagate_panic( - result: Result, tokio::task::JoinError>, -) -> Result { - match result { - Ok(ret) => ret, - Err(e) => match e.try_into_panic() { - Ok(payload) => std::panic::resume_unwind(payload), - Err(e) => Err(CacheError::Other(e.into())), - }, + if let Some(parent) = path.parent() { + if let Err(e) = std::fs::create_dir_all(parent) { + tracing::warn!( + dir=%parent.display(), + error=&e as &dyn std::error::Error, + "Unable to create the cache dir", + ); + } + } + + // PERF: We can reduce disk usage by using the weezl crate to + // LZW-encode the serialized module. + module + .serialize_to_file(&path) + .map_err(|e| CacheError::Other(Box::new(SerializeError { path, inner: e }))) } } @@ -136,7 +93,6 @@ mod tests { use tempfile::TempDir; use super::*; - use crate::runtime::task_manager::tokio::TokioTaskManager; const ADD_WAT: &[u8] = br#"( module @@ -150,21 +106,19 @@ mod tests { #[tokio::test] async fn save_to_disk() { - let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); let temp = TempDir::new().unwrap(); let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); let cache = OnDiskCache::new(temp.path()); let key = "wat"; - cache.save(key, &module, &task_manager).await.unwrap(); + cache.save(key, &module).await.unwrap(); assert!(temp.path().join(key).with_extension("bin").exists()); } #[tokio::test] async fn create_cache_dir_automatically() { - let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); let temp = TempDir::new().unwrap(); let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); @@ -173,27 +127,25 @@ mod tests { let cache = OnDiskCache::new(&cache_dir); let key = "wat"; - cache.save(key, &module, &task_manager).await.unwrap(); + cache.save(key, &module).await.unwrap(); assert!(cache_dir.is_dir()); } #[tokio::test] async fn missing_file() { - let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); let temp = TempDir::new().unwrap(); let engine = Engine::default(); let key = "wat"; let cache = OnDiskCache::new(temp.path()); - let err = cache.load(key, &engine, &task_manager).await.unwrap_err(); + let err = cache.load(key, &engine).await.unwrap_err(); assert!(matches!(err, CacheError::NotFound)); } #[tokio::test] async fn load_from_disk() { - let task_manager = TokioTaskManager::new(tokio::runtime::Handle::current()); let temp = TempDir::new().unwrap(); let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); @@ -203,7 +155,7 @@ mod tests { .unwrap(); let cache = OnDiskCache::new(temp.path()); - let module = cache.load(key, &engine, &task_manager).await.unwrap(); + let module = cache.load(key, &engine).await.unwrap(); let exports: Vec<_> = module .exports() diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index cf73c6e699c..6512b7f8b59 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -3,10 +3,7 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLock; use wasmer::{Engine, Module}; -use crate::{ - runtime::module_cache::{CacheError, CompiledModuleCache}, - VirtualTaskManager, -}; +use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; /// A [`CompiledModuleCache`] based on a /// [Arc]<[RwLock]<[HashMap]<[String], [Module]>>> that can be @@ -28,23 +25,13 @@ impl SharedCache { #[async_trait::async_trait] impl CompiledModuleCache for SharedCache { - async fn load( - &self, - key: &str, - _engine: &Engine, - _task_manager: &dyn VirtualTaskManager, - ) -> Result { + async fn load(&self, key: &str, _engine: &Engine) -> Result { let modules = self.modules.read().await; modules.get(key).cloned().ok_or(CacheError::NotFound) } - async fn save( - &self, - key: &str, - module: &Module, - _task_manager: &dyn VirtualTaskManager, - ) -> Result<(), CacheError> { + async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { let module = module.clone(); let key = key.to_string(); diff --git a/lib/wasi/src/runtime/module_cache/thread_local.rs b/lib/wasi/src/runtime/module_cache/thread_local.rs index e93a565b059..18a6ceed4e7 100644 --- a/lib/wasi/src/runtime/module_cache/thread_local.rs +++ b/lib/wasi/src/runtime/module_cache/thread_local.rs @@ -2,10 +2,7 @@ use std::{cell::RefCell, collections::HashMap}; use wasmer::{Engine, Module}; -use crate::runtime::{ - module_cache::{CacheError, CompiledModuleCache}, - VirtualTaskManager, -}; +use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; std::thread_local! { static CACHED_MODULES: RefCell> @@ -29,21 +26,11 @@ impl ThreadLocalCache { #[async_trait::async_trait] impl CompiledModuleCache for ThreadLocalCache { - async fn load( - &self, - key: &str, - _engine: &Engine, - _task_manager: &dyn VirtualTaskManager, - ) -> Result { + async fn load(&self, key: &str, _engine: &Engine) -> Result { self.lookup(key).ok_or(CacheError::NotFound) } - async fn save( - &self, - key: &str, - module: &Module, - _task_manager: &dyn VirtualTaskManager, - ) -> Result<(), CacheError> { + async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { self.insert(key, module); Ok(()) } diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index 0d4e2ec931e..f32a96ff832 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -2,7 +2,7 @@ use std::{fmt::Debug, ops::Deref}; use wasmer::{Engine, Module}; -use crate::runtime::{module_cache::AndThen, VirtualTaskManager}; +use crate::runtime::module_cache::AndThen; /// A cache for compiled WebAssembly modules. /// @@ -16,19 +16,9 @@ use crate::runtime::{module_cache::AndThen, VirtualTaskManager}; /// their caching strategy accordingly. #[async_trait::async_trait] pub trait CompiledModuleCache: Debug + Send + Sync { - async fn load( - &self, - key: &str, - engine: &Engine, - task_manager: &dyn VirtualTaskManager, - ) -> Result; + async fn load(&self, key: &str, engine: &Engine) -> Result; - async fn save( - &self, - key: &str, - module: &Module, - task_manager: &dyn VirtualTaskManager, - ) -> Result<(), CacheError>; + async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError>; /// Chain a second cache onto this one. /// @@ -59,21 +49,12 @@ where D: Deref + Debug + Send + Sync, C: CompiledModuleCache + ?Sized, { - async fn load( - &self, - key: &str, - engine: &Engine, - task_manager: &dyn VirtualTaskManager, - ) -> Result { - (**self).load(key, engine, task_manager).await + async fn load(&self, key: &str, engine: &Engine) -> Result { + (**self).load(key, engine).await } - async fn save( - &self, - key: &str, - module: &Module, - task_manager: &dyn VirtualTaskManager, - ) -> Result<(), CacheError> { - (**self).save(key, module, task_manager).await + + async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { + (**self).save(key, module).await } } From 86ee27c675d8c4abf57bdbfa6f8079b29db50b97 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Fri, 5 May 2023 11:06:34 +0800 Subject: [PATCH 06/18] Switch everything from ModuleCache to the CompiledModuleCache trait --- lib/cli/src/commands/run_unstable.rs | 29 +++-- lib/wasi/src/bin_factory/exec.rs | 32 +++-- lib/wasi/src/bin_factory/mod.rs | 11 +- lib/wasi/src/bin_factory/module_cache.rs | 123 ------------------ .../src/os/command/builtins/cmd_wasmer.rs | 95 +++++++------- lib/wasi/src/os/command/mod.rs | 9 +- lib/wasi/src/os/console/mod.rs | 37 +++--- lib/wasi/src/runtime/mod.rs | 18 ++- lib/wasi/src/runtime/module_cache/and_then.rs | 10 +- lib/wasi/src/runtime/module_cache/disabled.rs | 18 +++ lib/wasi/src/runtime/module_cache/mod.rs | 9 +- lib/wasi/src/runtime/module_cache/on_disk.rs | 4 +- lib/wasi/src/runtime/module_cache/shared.rs | 6 +- .../src/runtime/module_cache/thread_local.rs | 4 +- lib/wasi/src/runtime/module_cache/types.rs | 10 +- lib/wasi/src/runtime/resolver/cache.rs | 2 +- lib/wasi/src/runtime/resolver/types.rs | 4 +- lib/wasi/src/state/builder.rs | 16 +-- lib/wasi/src/state/env.rs | 12 +- .../snapshot__snapshot_fork.snap.new | 29 +++++ 20 files changed, 200 insertions(+), 278 deletions(-) delete mode 100644 lib/wasi/src/bin_factory/module_cache.rs create mode 100644 lib/wasi/src/runtime/module_cache/disabled.rs create mode 100644 tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new diff --git a/lib/cli/src/commands/run_unstable.rs b/lib/cli/src/commands/run_unstable.rs index 0a545a9154e..e5cc1df9c84 100644 --- a/lib/cli/src/commands/run_unstable.rs +++ b/lib/cli/src/commands/run_unstable.rs @@ -454,17 +454,24 @@ impl TargetOnDisk { let leading_bytes = &buffer[..bytes_read]; if wasmer::is_wasm(leading_bytes) { - Ok(TargetOnDisk::WebAssemblyBinary(path)) - } else if webc::detect(leading_bytes).is_ok() { - Ok(TargetOnDisk::Webc(path)) - } else if path.extension() == Some("wat".as_ref()) { - Ok(TargetOnDisk::Wat(path)) - } else { - #[cfg(feature = "compiler")] - if ArtifactBuild::is_deserializable(leading_bytes) { - return Ok(TargetOnDisk::Artifact(path)); - } - anyhow::bail!("Unable to determine how to execute \"{}\"", path.display()); + return Ok(TargetOnDisk::WebAssemblyBinary(path)); + } + + if webc::detect(leading_bytes).is_ok() { + return Ok(TargetOnDisk::Webc(path)); + } + + #[cfg(feature = "compiler")] + if ArtifactBuild::is_deserializable(leading_bytes) { + return Ok(TargetOnDisk::Artifact(path)); + } + + // If we can't figure out the file type based on its content, fall back + // to checking the extension. + + match path.extension().and_then(|s| s.to_str()) { + Some("wat") => Ok(TargetOnDisk::Wat(path)), + _ => anyhow::bail!("Unable to determine how to execute \"{}\"", path.display()), } } diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index 0e626d7fbe5..f8323f4743c 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -2,6 +2,7 @@ use std::{pin::Pin, sync::Arc}; use crate::{ os::task::{thread::WasiThreadRunGuard, TaskJoinHandle}, + runtime::module_cache::ModuleCache, VirtualBusError, WasiRuntimeError, }; use futures::Future; @@ -9,7 +10,7 @@ use tracing::*; use wasmer::{FunctionEnvMut, Instance, Memory, Module, Store}; use wasmer_wasix_types::wasi::Errno; -use super::{BinFactory, BinaryPackage, ModuleCache}; +use super::{BinFactory, BinaryPackage}; use crate::{ import_object_for_all_wasi_versions, runtime::SpawnType, SpawnedMemory, WasiEnv, WasiFunctionEnv, WasiRuntime, @@ -22,14 +23,14 @@ pub async fn spawn_exec( store: Store, env: WasiEnv, runtime: &Arc, - compiled_modules: &ModuleCache, ) -> Result { // The deterministic id for this engine let compiler = store.engine().deterministic_id(); - let module = compiled_modules - .get_compiled_module(&**runtime, binary.hash().as_str(), compiler) - .await; + let key = format!("{}-{}", binary.hash().as_str(), compiler); + + let compiled_modules = runtime.module_cache(); + let module = compiled_modules.load(&key, store.engine()).await.ok(); let module = match (module, binary.entry.as_ref()) { (Some(a), _) => a, @@ -48,9 +49,14 @@ pub async fn spawn_exec( } let module = module?; - compiled_modules - .set_compiled_module(binary.hash().as_str(), compiler, &module) - .await; + if let Err(e) = compiled_modules.save(&key, &module).await { + tracing::debug!( + %key, + package_name=%binary.package_name, + error=&e as &dyn std::error::Error, + "Unable to save the compiled module", + ); + } module } (None, None) => { @@ -212,15 +218,7 @@ impl BinFactory { let binary = binary?; // Execute - spawn_exec( - binary, - name.as_str(), - store, - env, - &self.runtime, - &self.cache, - ) - .await + spawn_exec(binary, name.as_str(), store, env, &self.runtime).await }) } diff --git a/lib/wasi/src/bin_factory/mod.rs b/lib/wasi/src/bin_factory/mod.rs index 41870e3884a..f50eed7a158 100644 --- a/lib/wasi/src/bin_factory/mod.rs +++ b/lib/wasi/src/bin_factory/mod.rs @@ -10,14 +10,12 @@ use virtual_fs::{AsyncReadExt, FileSystem}; mod binary_package; mod exec; -mod module_cache; use sha2::*; pub use self::{ binary_package::*, exec::{spawn_exec, spawn_exec_module}, - module_cache::ModuleCache, }; use crate::{os::command::Commands, WasiRuntime}; @@ -25,19 +23,14 @@ use crate::{os::command::Commands, WasiRuntime}; pub struct BinFactory { pub(crate) commands: Commands, runtime: Arc, - pub(crate) cache: Arc, pub(crate) local: Arc>>>, } impl BinFactory { - pub fn new( - compiled_modules: Arc, - runtime: Arc, - ) -> BinFactory { + pub fn new(runtime: Arc) -> BinFactory { BinFactory { - commands: Commands::new_with_builtins(runtime.clone(), compiled_modules.clone()), + commands: Commands::new_with_builtins(runtime.clone()), runtime, - cache: compiled_modules, local: Arc::new(RwLock::new(HashMap::new())), } } diff --git a/lib/wasi/src/bin_factory/module_cache.rs b/lib/wasi/src/bin_factory/module_cache.rs deleted file mode 100644 index da031ec1801..00000000000 --- a/lib/wasi/src/bin_factory/module_cache.rs +++ /dev/null @@ -1,123 +0,0 @@ -use std::path::PathBuf; - -use anyhow::Context; -use wasmer::Module; - -use super::BinaryPackage; -use crate::{runtime::module_cache::CompiledModuleCache, WasiRuntime}; - -pub const DEFAULT_COMPILED_PATH: &str = "~/.wasmer/compiled"; - -#[derive(Debug)] -pub struct ModuleCache(Box); - -// FIXME: remove impls! -// Added as a stopgap to get the crate to compile again with the "js" feature. -// wasmer::Module holds a JsValue, which makes it non-sync. -#[cfg(feature = "js")] -unsafe impl Send for ModuleCache {} -#[cfg(feature = "js")] -unsafe impl Sync for ModuleCache {} - -impl Default for ModuleCache { - fn default() -> Self { - ModuleCache::new(None, true) - } -} - -impl ModuleCache { - /// Create a new [`ModuleCache`]. - /// - /// use_shared_cache enables a shared cache of modules in addition to a thread-local cache. - pub fn new(cache_compile_dir: Option, _use_shared_cache: bool) -> ModuleCache { - let cache_compile_dir = cache_compile_dir.unwrap_or_else(|| { - PathBuf::from(shellexpand::tilde(DEFAULT_COMPILED_PATH).into_owned()) - }); - - let cache = crate::runtime::module_cache::default_cache(&cache_compile_dir); - - ModuleCache(Box::new(cache)) - } - - pub fn get_webc( - &self, - webc: &str, - runtime: &dyn WasiRuntime, - ) -> Result { - let ident = webc.parse().context("Unable to parse the package name")?; - let resolver = runtime.package_resolver(); - let client = runtime - .http_client() - .context("No HTTP client available")? - .clone(); - - runtime.task_manager().block_on(async move { - resolver - .resolve_package(&ident, &*client) - .await - .with_context(|| format!("An error occurred while fetching \"{webc}\"")) - }) - } - - pub async fn get_compiled_module( - &self, - runtime: &dyn WasiRuntime, - data_hash: &str, - compiler: &str, - ) -> Option { - let key = format!("{}-{}", data_hash, compiler); - let engine = runtime.engine()?; - - self.0.load(&key, &engine).await.ok() - } - - pub async fn set_compiled_module(&self, data_hash: &str, compiler: &str, module: &Module) { - let key = format!("{}-{}", data_hash, compiler); - - let result = self.0.save(&key, module).await; - - if let Err(e) = result { - tracing::warn!( - data_hash, - compiler, - error = &e as &dyn std::error::Error, - "Unable to cache the module", - ); - } - } -} - -#[cfg(test)] -#[cfg(feature = "sys")] -mod tests { - use std::{sync::Arc, time::Duration}; - - use tracing_subscriber::filter::LevelFilter; - - use crate::{runtime::task_manager::tokio::TokioTaskManager, PluggableRuntime}; - - use super::*; - - #[test] - fn test_module_cache() { - let _ = tracing_subscriber::fmt() - .pretty() - .with_test_writer() - .with_max_level(LevelFilter::INFO) - .try_init(); - - let cache = ModuleCache::new(None, true); - - let rt = PluggableRuntime::new(Arc::new(TokioTaskManager::shared())); - let tasks = rt.task_manager(); - - let mut store = Vec::new(); - for _ in 0..2 { - let webc = cache.get_webc("sharrattj/dash", &rt).unwrap(); - store.push(webc); - tasks - .runtime() - .block_on(tasks.sleep_now(Duration::from_secs(1))); - } - } -} diff --git a/lib/wasi/src/os/command/builtins/cmd_wasmer.rs b/lib/wasi/src/os/command/builtins/cmd_wasmer.rs index 39fc9be89b9..c5533a61c8b 100644 --- a/lib/wasi/src/os/command/builtins/cmd_wasmer.rs +++ b/lib/wasi/src/os/command/builtins/cmd_wasmer.rs @@ -1,4 +1,4 @@ -use std::{any::Any, ops::Deref, sync::Arc}; +use std::{any::Any, sync::Arc}; use crate::{ os::task::{OwnedTaskStatus, TaskJoinHandle}, @@ -8,7 +8,7 @@ use wasmer::{FunctionEnvMut, Store}; use wasmer_wasix_types::wasi::Errno; use crate::{ - bin_factory::{spawn_exec, BinaryPackage, ModuleCache}, + bin_factory::{spawn_exec, BinaryPackage}, syscalls::stderr_write, VirtualTaskManagerExt, WasiEnv, WasiRuntime, }; @@ -36,25 +36,18 @@ use crate::os::command::VirtualCommand; #[derive(Debug, Clone)] pub struct CmdWasmer { runtime: Arc, - cache: Arc, } impl CmdWasmer { const NAME: &str = "wasmer"; - pub fn new( - runtime: Arc, - compiled_modules: Arc, - ) -> Self { - Self { - runtime, - cache: compiled_modules, - } + pub fn new(runtime: Arc) -> Self { + Self { runtime } } } impl CmdWasmer { - fn run<'a>( + async fn run<'a>( &self, parent_ctx: &FunctionEnvMut<'a, WasiEnv>, name: &str, @@ -78,36 +71,31 @@ impl CmdWasmer { state.args = args; env.state = Arc::new(state); - // Get the binary - if let Some(binary) = self.get_package(what.clone()) { + if let Some(binary) = self.get_package(what.clone()).await { // Now run the module - parent_ctx.data().tasks().block_on(async { - spawn_exec(binary, name, store, env, &self.runtime, &self.cache).await - }) + spawn_exec(binary, name, store, env, &self.runtime).await } else { - parent_ctx.data().tasks().block_on(async move { - let _ = stderr_write( - parent_ctx, - format!("package not found - {}\r\n", what).as_bytes(), - ) - .await; - }); + let _ = stderr_write( + parent_ctx, + format!("package not found - {}\r\n", what).as_bytes(), + ) + .await; let handle = OwnedTaskStatus::new_finished_with_code(Errno::Noent.into()).handle(); Ok(handle) } + // Get the binary } else { - parent_ctx.data().tasks().block_on(async move { - let _ = stderr_write(parent_ctx, HELP_RUN.as_bytes()).await; - }); + let _ = stderr_write(parent_ctx, HELP_RUN.as_bytes()).await; let handle = OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle(); Ok(handle) } } - pub fn get_package(&self, name: String) -> Option { - self.cache - .get_webc(name.as_str(), self.runtime.deref()) - .ok() + pub async fn get_package(&self, name: String) -> Option { + let resolver = self.runtime.package_resolver(); + let client = self.runtime.http_client()?; + let pkg = name.parse().ok()?; + resolver.resolve_package(&pkg, &client).await.ok() } } @@ -129,30 +117,35 @@ impl VirtualCommand for CmdWasmer { ) -> Result { // Read the command we want to run let env_inner = env.as_ref().ok_or(VirtualBusError::UnknownError)?; - let mut args = env_inner.state.args.iter().map(|a| a.as_str()); + let args = env_inner.state.args.clone(); + let mut args = args.iter().map(|s| s.as_str()); let _alias = args.next(); let cmd = args.next(); // Check the command - match cmd { - Some("run") => { - let what = args.next().map(|a| a.to_string()); - let args = args.map(|a| a.to_string()).collect(); - self.run(parent_ctx, name, store, env, what, args) - } - Some("--help") | None => { - parent_ctx.data().tasks().block_on(async move { - let _ = stderr_write(parent_ctx, HELP.as_bytes()).await; - }); - let handle = - OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle(); - Ok(handle) - } - Some(what) => { - let what = Some(what.to_string()); - let args = args.map(|a| a.to_string()).collect(); - self.run(parent_ctx, name, store, env, what, args) + let fut = async { + match cmd { + Some("run") => { + let what = args.next().map(|a| a.to_string()); + let args = args.map(|a| a.to_string()).collect(); + self.run(parent_ctx, name, store, env, what, args).await + } + Some("--help") | None => { + parent_ctx.data().tasks().block_on(async move { + let _ = stderr_write(parent_ctx, HELP.as_bytes()).await; + }); + let handle = + OwnedTaskStatus::new_finished_with_code(Errno::Success.into()).handle(); + Ok(handle) + } + Some(what) => { + let what = Some(what.to_string()); + let args = args.map(|a| a.to_string()).collect(); + self.run(parent_ctx, name, store, env, what, args).await + } } - } + }; + + parent_ctx.data().tasks().block_on(fut) } } diff --git a/lib/wasi/src/os/command/mod.rs b/lib/wasi/src/os/command/mod.rs index b2723dbb1eb..32abb2f0594 100644 --- a/lib/wasi/src/os/command/mod.rs +++ b/lib/wasi/src/os/command/mod.rs @@ -6,7 +6,7 @@ use wasmer::{FunctionEnvMut, Store}; use wasmer_wasix_types::wasi::Errno; use crate::{ - bin_factory::ModuleCache, syscalls::stderr_write, VirtualBusError, WasiEnv, WasiRuntime, + syscalls::stderr_write, VirtualBusError, WasiEnv, WasiRuntime, }; use super::task::{OwnedTaskStatus, TaskJoinHandle, TaskStatus}; @@ -45,12 +45,9 @@ impl Commands { } // TODO: this method should be somewhere on the runtime, not here. - pub fn new_with_builtins( - runtime: Arc, - compiled_modules: Arc, - ) -> Self { + pub fn new_with_builtins(runtime: Arc) -> Self { let mut cmd = Self::new(); - let cmd_wasmer = builtins::cmd_wasmer::CmdWasmer::new(runtime.clone(), compiled_modules); + let cmd_wasmer = builtins::cmd_wasmer::CmdWasmer::new(runtime.clone()); cmd.register_command(cmd_wasmer); cmd diff --git a/lib/wasi/src/os/console/mod.rs b/lib/wasi/src/os/console/mod.rs index a758230f1f3..1a0d5e80db1 100644 --- a/lib/wasi/src/os/console/mod.rs +++ b/lib/wasi/src/os/console/mod.rs @@ -26,9 +26,10 @@ use wasmer_wasix_types::{types::__WASI_STDIN_FILENO, wasi::BusErrno}; use super::{cconst::ConsoleConst, common::*, task::TaskJoinHandle}; use crate::{ - bin_factory::{spawn_exec, BinFactory, ModuleCache}, + bin_factory::{spawn_exec, BinFactory}, capabilities::Capabilities, os::task::{control_plane::WasiControlPlane, process::WasiProcess}, + runtime::resolver::WebcIdentifier, VirtualBusError, VirtualTaskManagerExt, WasiEnv, WasiRuntime, }; @@ -46,7 +47,6 @@ pub struct Console { prompt: String, env: HashMap, runtime: Arc, - compiled_modules: Arc, stdin: ArcBoxFile, stdout: ArcBoxFile, stderr: ArcBoxFile, @@ -57,7 +57,6 @@ impl Console { pub fn new( webc_boot_package: &str, runtime: Arc, - compiled_modules: Arc, ) -> Self { let prog = webc_boot_package .split_once(' ') @@ -78,7 +77,6 @@ impl Console { env: HashMap::new(), runtime, prompt: "wasmer.sh".to_string(), - compiled_modules, stdin: ArcBoxFile::new(Box::new(Pipe::channel().0)), stdout: ArcBoxFile::new(Box::new(Pipe::channel().0)), stderr: ArcBoxFile::new(Box::new(Pipe::channel().0)), @@ -186,7 +184,6 @@ impl Console { .unwrap() .stdout(Box::new(self.stdout.clone())) .stderr(Box::new(self.stderr.clone())) - .compiled_modules(self.compiled_modules.clone()) .runtime(self.runtime.clone()) .capabilities(self.capabilities.clone()) .build_init() @@ -203,8 +200,25 @@ impl Console { tasks.block_on(self.draw_welcome()); } - let binary = if let Ok(binary) = self.compiled_modules.get_webc(webc, self.runtime.deref()) - { + let webc_ident: WebcIdentifier = match webc.parse() { + Ok(ident) => ident, + Err(e) => { + tracing::debug!(webc, error = &*e, "Unable to parse the WEBC identifier"); + return Err(VirtualBusError::BadRequest); + } + }; + let client = self + .runtime + .http_client() + .ok_or(VirtualBusError::UnknownError)?; + + let resolved_package = tasks.block_on( + self.runtime + .package_resolver() + .resolve_package(&webc_ident, &client), + ); + + let binary = if let Ok(binary) = resolved_package { binary } else { let mut stderr = self.stderr.clone(); @@ -242,14 +256,7 @@ impl Console { // Build the config // Run the binary - let process = tasks.block_on(spawn_exec( - binary, - prog, - store, - env, - &self.runtime, - self.compiled_modules.as_ref(), - ))?; + let process = tasks.block_on(spawn_exec(binary, prog, store, env, &self.runtime))?; // Return the process Ok((process, wasi_process)) diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index 1cae0088310..0f13bbae664 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -15,7 +15,10 @@ use virtual_net::{DynVirtualNetworking, VirtualNetworking}; use crate::{ http::DynHttpClient, os::TtyBridge, - runtime::resolver::{PackageResolver, RegistryResolver}, + runtime::{ + module_cache::{ModuleCache, SharedCache}, + resolver::{PackageResolver, RegistryResolver}, + }, WasiTtyState, }; @@ -35,6 +38,13 @@ where fn package_resolver(&self) -> Arc; + /// A cache for compiled modules. + /// + /// Caching is disabled by default. + fn module_cache(&self) -> Arc { + Arc::new(module_cache::Disabled) + } + /// Get a [`wasmer::Engine`] for module compilation. fn engine(&self) -> Option { None @@ -98,6 +108,7 @@ pub struct PluggableRuntime { pub http_client: Option, pub resolver: Arc, pub engine: Option, + pub module_cache: Arc, #[derivative(Debug = "ignore")] pub tty: Option>, } @@ -125,6 +136,7 @@ impl PluggableRuntime { engine: None, tty: None, resolver: Arc::new(resolver), + module_cache: Arc::new(SharedCache::default()), } } @@ -182,4 +194,8 @@ impl WasiRuntime for PluggableRuntime { fn tty(&self) -> Option<&(dyn TtyBridge + Send + Sync)> { self.tty.as_deref() } + + fn module_cache(&self) -> Arc { + self.module_cache.clone() + } } diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs index 6a40de4a8f0..e68a86c9ff0 100644 --- a/lib/wasi/src/runtime/module_cache/and_then.rs +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -1,8 +1,8 @@ use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache}; -/// A [`CompiledModuleCache`] combinator which will try operations on one cache +/// A [`ModuleCache`] combinator which will try operations on one cache /// and fall back to a secondary cache if they fail. /// /// Constructed via [`CompiledModuleCache::and_then()`]. @@ -40,10 +40,10 @@ impl AndThen { } #[async_trait::async_trait] -impl CompiledModuleCache for AndThen +impl ModuleCache for AndThen where - Primary: CompiledModuleCache + Send + Sync, - Secondary: CompiledModuleCache + Send + Sync, + Primary: ModuleCache + Send + Sync, + Secondary: ModuleCache + Send + Sync, { async fn load(&self, key: &str, engine: &Engine) -> Result { let primary_error = match self.primary.load(key, engine).await { diff --git a/lib/wasi/src/runtime/module_cache/disabled.rs b/lib/wasi/src/runtime/module_cache/disabled.rs new file mode 100644 index 00000000000..c1f25a049a2 --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/disabled.rs @@ -0,0 +1,18 @@ +use wasmer::{Engine, Module}; + +use crate::runtime::module_cache::{CacheError, ModuleCache}; + +/// A cache that always fails. +#[derive(Debug, Copy, Clone, PartialEq, Default)] +pub(crate) struct Disabled; + +#[async_trait::async_trait] +impl ModuleCache for Disabled { + async fn load(&self, _key: &str, _engine: &Engine) -> Result { + Err(CacheError::NotFound) + } + + async fn save(&self, _key: &str, _module: &Module) -> Result<(), CacheError> { + Err(CacheError::NotFound) + } +} diff --git a/lib/wasi/src/runtime/module_cache/mod.rs b/lib/wasi/src/runtime/module_cache/mod.rs index 915c68ff1f0..111af57e8e5 100644 --- a/lib/wasi/src/runtime/module_cache/mod.rs +++ b/lib/wasi/src/runtime/module_cache/mod.rs @@ -1,4 +1,5 @@ mod and_then; +mod disabled; mod on_disk; mod shared; mod thread_local; @@ -9,15 +10,17 @@ pub use self::{ on_disk::OnDiskCache, shared::SharedCache, thread_local::ThreadLocalCache, - types::{CacheError, CompiledModuleCache}, + types::{CacheError, ModuleCache}, }; -/// Get a [`CompiledModuleCache`] which should be good enough for most use +pub(crate) use self::disabled::Disabled; + +/// Get a [`ModuleCache`] which should be good enough for most use /// cases. /// /// The returned object will use a mix of in-memory and on-disk caching /// strategies. -pub fn default_cache(cache_dir: &std::path::Path) -> impl CompiledModuleCache { +pub fn default_cache(cache_dir: &std::path::Path) -> impl ModuleCache + Send + Sync { ThreadLocalCache::default() .and_then(SharedCache::default()) .and_then(OnDiskCache::new(cache_dir)) diff --git a/lib/wasi/src/runtime/module_cache/on_disk.rs b/lib/wasi/src/runtime/module_cache/on_disk.rs index 8d6bb85edde..6311e9aa93e 100644 --- a/lib/wasi/src/runtime/module_cache/on_disk.rs +++ b/lib/wasi/src/runtime/module_cache/on_disk.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache}; /// A cache that saves modules to a folder on disk using /// [`Module::serialize()`]. @@ -31,7 +31,7 @@ impl OnDiskCache { } #[async_trait::async_trait] -impl CompiledModuleCache for OnDiskCache { +impl ModuleCache for OnDiskCache { async fn load(&self, key: &str, engine: &Engine) -> Result { let path = self.path(key); diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index 6512b7f8b59..69810be6aa0 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -3,9 +3,9 @@ use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLock; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache}; -/// A [`CompiledModuleCache`] based on a +/// A [`ModuleCache`] based on a /// [Arc]<[RwLock]<[HashMap]<[String], [Module]>>> that can be /// shared. #[derive(Debug, Default, Clone)] @@ -24,7 +24,7 @@ impl SharedCache { } #[async_trait::async_trait] -impl CompiledModuleCache for SharedCache { +impl ModuleCache for SharedCache { async fn load(&self, key: &str, _engine: &Engine) -> Result { let modules = self.modules.read().await; diff --git a/lib/wasi/src/runtime/module_cache/thread_local.rs b/lib/wasi/src/runtime/module_cache/thread_local.rs index 18a6ceed4e7..8d1809e7f7a 100644 --- a/lib/wasi/src/runtime/module_cache/thread_local.rs +++ b/lib/wasi/src/runtime/module_cache/thread_local.rs @@ -2,7 +2,7 @@ use std::{cell::RefCell, collections::HashMap}; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, CompiledModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache}; std::thread_local! { static CACHED_MODULES: RefCell> @@ -25,7 +25,7 @@ impl ThreadLocalCache { } #[async_trait::async_trait] -impl CompiledModuleCache for ThreadLocalCache { +impl ModuleCache for ThreadLocalCache { async fn load(&self, key: &str, _engine: &Engine) -> Result { self.lookup(key).ok_or(CacheError::NotFound) } diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index f32a96ff832..c739a42b379 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -15,7 +15,7 @@ use crate::runtime::module_cache::AndThen; /// be called more often than [`CompiledModuleCache::save()`] and optimise /// their caching strategy accordingly. #[async_trait::async_trait] -pub trait CompiledModuleCache: Debug + Send + Sync { +pub trait ModuleCache: Debug { async fn load(&self, key: &str, engine: &Engine) -> Result; async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError>; @@ -37,17 +37,17 @@ pub trait CompiledModuleCache: Debug + Send + Sync { fn and_then(self, other: C) -> AndThen where Self: Sized, - C: CompiledModuleCache, + C: ModuleCache, { AndThen::new(self, other) } } #[async_trait::async_trait] -impl CompiledModuleCache for D +impl ModuleCache for D where D: Deref + Debug + Send + Sync, - C: CompiledModuleCache + ?Sized, + C: ModuleCache + Send + Sync + ?Sized, { async fn load(&self, key: &str, engine: &Engine) -> Result { (**self).load(key, engine).await @@ -73,6 +73,6 @@ mod tests { #[test] fn is_object_safe() { - let _: Option> = None; + let _: Option> = None; } } diff --git a/lib/wasi/src/runtime/resolver/cache.rs b/lib/wasi/src/runtime/resolver/cache.rs index 1517cda3982..f7cd377c9e5 100644 --- a/lib/wasi/src/runtime/resolver/cache.rs +++ b/lib/wasi/src/runtime/resolver/cache.rs @@ -59,7 +59,7 @@ impl InMemoryCache { #[async_trait::async_trait] impl PackageResolver for InMemoryCache where - R: PackageResolver, + R: PackageResolver + Send + Sync, { async fn resolve_package( &self, diff --git a/lib/wasi/src/runtime/resolver/types.rs b/lib/wasi/src/runtime/resolver/types.rs index 566c43656a2..19ef9380d52 100644 --- a/lib/wasi/src/runtime/resolver/types.rs +++ b/lib/wasi/src/runtime/resolver/types.rs @@ -12,7 +12,7 @@ use semver::VersionReq; use crate::{bin_factory::BinaryPackage, http::HttpClient, runtime::resolver::InMemoryCache}; #[async_trait::async_trait] -pub trait PackageResolver: Debug + Send + Sync { +pub trait PackageResolver: Debug { /// Resolve a package, loading all dependencies. async fn resolve_package( &self, @@ -33,7 +33,7 @@ pub trait PackageResolver: Debug + Send + Sync { impl PackageResolver for D where D: Deref + Debug + Send + Sync, - R: PackageResolver + ?Sized, + R: PackageResolver + Send + Sync + ?Sized, { /// Resolve a package, loading all dependencies. async fn resolve_package( diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index d2ff6cf1f1f..ba84378482f 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -15,7 +15,7 @@ use wasmer_wasix_types::wasi::Errno; #[cfg(feature = "sys")] use crate::PluggableRuntime; use crate::{ - bin_factory::{BinFactory, ModuleCache}, + bin_factory::BinFactory, capabilities::Capabilities, fs::{WasiFs, WasiFsRoot, WasiInodes}, os::task::control_plane::{ControlPlaneConfig, ControlPlaneError, WasiControlPlane}, @@ -52,7 +52,6 @@ pub struct WasiEnvBuilder { pub(super) preopens: Vec, /// Pre-opened virtual directories that will be accessible from WASI. vfs_preopens: Vec, - pub(super) compiled_modules: Option>, #[allow(clippy::type_complexity)] pub(super) setup_fs_fn: Option Result<(), String> + Send>>, @@ -547,13 +546,6 @@ impl WasiEnvBuilder { self.runtime = Some(runtime); } - /// Sets the compiled modules to use with this builder (sharing the - /// cached modules is better for performance and memory consumption) - pub fn compiled_modules(mut self, compiled_modules: Arc) -> Self { - self.compiled_modules = Some(compiled_modules); - self - } - pub fn capabilities(mut self, capabilities: Capabilities) -> Self { self.set_capabilities(capabilities); self @@ -701,9 +693,6 @@ impl WasiEnvBuilder { envs, }; - // TODO: this method should not exist - must have unified construction flow! - let module_cache = self.compiled_modules.unwrap_or_default(); - let runtime = self.runtime.unwrap_or_else(|| { #[cfg(feature = "sys-thread")] { @@ -719,7 +708,7 @@ impl WasiEnvBuilder { let uses = self.uses; let map_commands = self.map_commands; - let bin_factory = BinFactory::new(module_cache.clone(), runtime.clone()); + let bin_factory = BinFactory::new(runtime.clone()); let capabilities = self.capabilites; @@ -731,7 +720,6 @@ impl WasiEnvBuilder { let init = WasiEnvInit { state, runtime, - module_cache, webc_dependencies: uses, mapped_commands: map_commands, control_plane, diff --git a/lib/wasi/src/state/env.rs b/lib/wasi/src/state/env.rs index ed24f00ba0d..883d8d19b20 100644 --- a/lib/wasi/src/state/env.rs +++ b/lib/wasi/src/state/env.rs @@ -16,7 +16,7 @@ use wasmer_wasix_types::{ }; use crate::{ - bin_factory::{BinFactory, ModuleCache}, + bin_factory::BinFactory, capabilities::Capabilities, fs::{WasiFsRoot, WasiInodes}, import_object_for_all_wasi_versions, @@ -203,7 +203,6 @@ unsafe impl Sync for WasiInstanceHandles {} pub struct WasiEnvInit { pub(crate) state: WasiState, pub runtime: Arc, - pub module_cache: Arc, pub webc_dependencies: Vec, pub mapped_commands: HashMap, pub bin_factory: BinFactory, @@ -245,7 +244,6 @@ impl WasiEnvInit { preopen: self.state.preopen.clone(), }, runtime: self.runtime.clone(), - module_cache: self.module_cache.clone(), webc_dependencies: self.webc_dependencies.clone(), mapped_commands: self.mapped_commands.clone(), bin_factory: self.bin_factory.clone(), @@ -286,7 +284,6 @@ pub struct WasiEnv { pub owned_handles: Vec, /// Implementation of the WASI runtime. pub runtime: Arc, - pub module_cache: Arc, pub capabilities: Capabilities, } @@ -332,7 +329,6 @@ impl WasiEnv { inner: self.inner.clone(), owned_handles: self.owned_handles.clone(), runtime: self.runtime.clone(), - module_cache: self.module_cache.clone(), capabilities: self.capabilities.clone(), } } @@ -363,7 +359,6 @@ impl WasiEnv { owned_handles: Vec::new(), runtime: self.runtime.clone(), capabilities: self.capabilities.clone(), - module_cache: self.module_cache.clone(), }; Ok((new_env, handle)) } @@ -402,7 +397,6 @@ impl WasiEnv { owned_handles: Vec::new(), runtime: init.runtime, bin_factory: init.bin_factory, - module_cache: init.module_cache.clone(), capabilities: init.capabilities, }; env.owned_handles.push(thread); @@ -775,10 +769,12 @@ impl WasiEnv { .get("/bin/wasmer") .and_then(|cmd| cmd.as_any().downcast_ref::()); + let tasks = self.runtime.task_manager(); + while let Some(use_package) = use_packages.pop_back() { if let Some(package) = cmd_wasmer .as_ref() - .and_then(|cmd| cmd.get_package(use_package.clone())) + .and_then(|cmd| tasks.block_on(cmd.get_package(use_package.clone()))) { // If its already been added make sure the version is correct let package_name = package.package_name.to_string(); diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new new file mode 100644 index 00000000000..eef6e6dd7fe --- /dev/null +++ b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new @@ -0,0 +1,29 @@ +--- +source: tests/integration/cli/tests/snapshot.rs +assertion_line: 714 +expression: snapshot +--- +{ + "spec": { + "name": "snapshot::test_snapshot_fork", + "use_packages": [ + "sharrattj/coreutils" + ], + "include_webcs": [ + { + "name": "sharrattj/coreutils@1.0.16" + } + ], + "cli_args": [], + "debug_output": false, + "enable_threads": true, + "enable_network": false + }, + "result": { + "Success": { + "stdout": "Parent has x = 0\nChild has x = 2\nParent memory is good\nChild memory is good\nChild(2) exited with 3\n", + "stderr": "", + "exit_code": 0 + } + } +} From fb059e009c59592e5438e04b26c9a5cbbf780012 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Fri, 5 May 2023 12:36:41 +0800 Subject: [PATCH 07/18] Switch SharedCache over to DashMap --- Cargo.lock | 1 + lib/wasi/Cargo.toml | 1 + lib/wasi/src/runtime/module_cache/shared.rs | 22 +++++++-------------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8e17cef1c7..006149241c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5726,6 +5726,7 @@ dependencies = [ "cfg-if 1.0.0", "chrono", "cooked-waker", + "dashmap", "derivative", "futures", "getrandom", diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index c82aadfafcf..bcd336ce725 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -61,6 +61,7 @@ tower-http = { version = "0.4.0", features = ["trace", "util", "catch-panic", "c tower = { version = "0.4.13", features = ["make", "util"], optional = true } url = "2.3.1" semver = "1.0.17" +dashmap = "5.4.0" [target.'cfg(not(target_arch = "riscv64"))'.dependencies.reqwest] version = "0.11" diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index 69810be6aa0..f40688b5481 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -1,6 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; - -use tokio::sync::RwLock; +use dashmap::DashMap; use wasmer::{Engine, Module}; use crate::runtime::module_cache::{CacheError, ModuleCache}; @@ -10,32 +8,26 @@ use crate::runtime::module_cache::{CacheError, ModuleCache}; /// shared. #[derive(Debug, Default, Clone)] pub struct SharedCache { - modules: Arc>>, + modules: DashMap, } impl SharedCache { pub fn new() -> SharedCache { SharedCache::default() } - - pub fn from_existing_modules(modules: Arc>>) -> Self { - SharedCache { modules } - } } #[async_trait::async_trait] impl ModuleCache for SharedCache { async fn load(&self, key: &str, _engine: &Engine) -> Result { - let modules = self.modules.read().await; - - modules.get(key).cloned().ok_or(CacheError::NotFound) + self.modules + .get(key) + .map(|m| m.value().clone()) + .ok_or(CacheError::NotFound) } async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { - let module = module.clone(); - let key = key.to_string(); - - self.modules.write().await.insert(key, module); + self.modules.insert(key.to_string(), module.clone()); Ok(()) } From 0667415dc4f0ce6708a8018081bdfb999871f58b Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Fri, 5 May 2023 13:23:18 +0800 Subject: [PATCH 08/18] Rename default_cache() to in_memory() and take browser-specific details into account --- lib/wasi/src/runtime/mod.rs | 2 +- lib/wasi/src/runtime/module_cache/mod.rs | 22 ++++++++++++++------- lib/wasi/src/runtime/module_cache/shared.rs | 4 +--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index 0f13bbae664..686c33272f4 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -136,7 +136,7 @@ impl PluggableRuntime { engine: None, tty: None, resolver: Arc::new(resolver), - module_cache: Arc::new(SharedCache::default()), + module_cache: Arc::new(module_cache::in_memory()), } } diff --git a/lib/wasi/src/runtime/module_cache/mod.rs b/lib/wasi/src/runtime/module_cache/mod.rs index 111af57e8e5..b4a36fa6e49 100644 --- a/lib/wasi/src/runtime/module_cache/mod.rs +++ b/lib/wasi/src/runtime/module_cache/mod.rs @@ -15,13 +15,21 @@ pub use self::{ pub(crate) use self::disabled::Disabled; -/// Get a [`ModuleCache`] which should be good enough for most use +/// Get a [`ModuleCache`] which should be good enough for most in-memory use /// cases. /// -/// The returned object will use a mix of in-memory and on-disk caching -/// strategies. -pub fn default_cache(cache_dir: &std::path::Path) -> impl ModuleCache + Send + Sync { - ThreadLocalCache::default() - .and_then(SharedCache::default()) - .and_then(OnDiskCache::new(cache_dir)) +/// # Platform-specific Notes +/// +/// This will use the [`ThreadLocalCache`] when running in the browser because +/// threads are run in separate workers. If you wish to share compiled modules +/// between threads, you will need to use a custom [`ModuleCache`] +/// implementation. +pub fn in_memory() -> impl ModuleCache + Send + Sync { + cfg_if::cfg_if! { + if #[cfg(feature = "js")] { + ThreadLocalCache::default() + } else { + SharedCache::default() + } + } } diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index f40688b5481..838fcf96e62 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -3,9 +3,7 @@ use wasmer::{Engine, Module}; use crate::runtime::module_cache::{CacheError, ModuleCache}; -/// A [`ModuleCache`] based on a -/// [Arc]<[RwLock]<[HashMap]<[String], [Module]>>> that can be -/// shared. +/// A [`ModuleCache`] based on a [DashMap]<[String], [Module]>. #[derive(Debug, Default, Clone)] pub struct SharedCache { modules: DashMap, From 5af1b6a017936ccbddba1498b503cfefb7e43834 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Fri, 5 May 2023 13:55:19 +0800 Subject: [PATCH 09/18] Switch cache keys from a String to a bespoke Key type --- lib/wasi/src/bin_factory/binary_package.rs | 24 +++--- lib/wasi/src/bin_factory/exec.rs | 6 +- lib/wasi/src/bin_factory/mod.rs | 9 --- lib/wasi/src/os/command/mod.rs | 4 +- lib/wasi/src/runtime/mod.rs | 2 +- lib/wasi/src/runtime/module_cache/and_then.rs | 12 +-- lib/wasi/src/runtime/module_cache/disabled.rs | 6 +- lib/wasi/src/runtime/module_cache/mod.rs | 13 ++-- lib/wasi/src/runtime/module_cache/on_disk.rs | 29 ++++---- lib/wasi/src/runtime/module_cache/shared.rs | 14 ++-- .../src/runtime/module_cache/thread_local.rs | 16 ++-- lib/wasi/src/runtime/module_cache/types.rs | 74 +++++++++++++++++-- lib/wasi/src/runtime/resolver/cache.rs | 4 +- lib/wasi/src/wapm/mod.rs | 5 +- .../snapshot__snapshot_fork.snap.new | 29 -------- 15 files changed, 138 insertions(+), 109 deletions(-) delete mode 100644 tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new diff --git a/lib/wasi/src/bin_factory/binary_package.rs b/lib/wasi/src/bin_factory/binary_package.rs index 19f32738d47..7924c4a69c4 100644 --- a/lib/wasi/src/bin_factory/binary_package.rs +++ b/lib/wasi/src/bin_factory/binary_package.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{Arc, RwLock}; use derivative::*; use once_cell::sync::OnceCell; @@ -6,7 +6,7 @@ use semver::Version; use virtual_fs::FileSystem; use webc::compat::SharedBytes; -use super::hash_of_binary; +use crate::runtime::module_cache::Key; #[derive(Derivative, Clone)] #[derivative(Debug)] @@ -14,7 +14,7 @@ pub struct BinaryPackageCommand { name: String, #[derivative(Debug = "ignore")] pub(crate) atom: SharedBytes, - hash: OnceCell, + hash: OnceCell, } impl BinaryPackageCommand { @@ -38,8 +38,8 @@ impl BinaryPackageCommand { &self.atom } - pub fn hash(&self) -> &str { - self.hash.get_or_init(|| hash_of_binary(self.atom())) + pub fn hash(&self) -> &Key { + self.hash.get_or_init(|| Key::sha256(self.atom())) } } @@ -54,7 +54,7 @@ pub struct BinaryPackage { pub when_cached: Option, #[derivative(Debug = "ignore")] pub entry: Option, - pub hash: Arc>>, + pub hash: OnceCell, pub webc_fs: Option>, pub commands: Arc>>, pub uses: Vec, @@ -64,15 +64,13 @@ pub struct BinaryPackage { } impl BinaryPackage { - pub fn hash(&self) -> String { - let mut hash = self.hash.lock().unwrap(); - if hash.is_none() { + pub fn hash(&self) -> Key { + *self.hash.get_or_init(|| { if let Some(entry) = self.entry.as_ref() { - hash.replace(hash_of_binary(entry.as_ref())); + Key::sha256(entry) } else { - hash.replace(hash_of_binary(&self.package_name)); + Key::sha256(self.package_name.as_bytes()) } - } - hash.as_ref().unwrap().clone() + }) } } diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index f8323f4743c..c29b1ddd73c 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -27,10 +27,10 @@ pub async fn spawn_exec( // The deterministic id for this engine let compiler = store.engine().deterministic_id(); - let key = format!("{}-{}", binary.hash().as_str(), compiler); + let key = binary.hash().combined_with(compiler); let compiled_modules = runtime.module_cache(); - let module = compiled_modules.load(&key, store.engine()).await.ok(); + let module = compiled_modules.load(key, store.engine()).await.ok(); let module = match (module, binary.entry.as_ref()) { (Some(a), _) => a, @@ -49,7 +49,7 @@ pub async fn spawn_exec( } let module = module?; - if let Err(e) = compiled_modules.save(&key, &module).await { + if let Err(e) = compiled_modules.save(key, &module).await { tracing::debug!( %key, package_name=%binary.package_name, diff --git a/lib/wasi/src/bin_factory/mod.rs b/lib/wasi/src/bin_factory/mod.rs index f50eed7a158..54dcce4a4e5 100644 --- a/lib/wasi/src/bin_factory/mod.rs +++ b/lib/wasi/src/bin_factory/mod.rs @@ -11,8 +11,6 @@ use virtual_fs::{AsyncReadExt, FileSystem}; mod binary_package; mod exec; -use sha2::*; - pub use self::{ binary_package::*, exec::{spawn_exec, spawn_exec_module}, @@ -111,10 +109,3 @@ async fn load_package_from_filesystem( Ok(pkg) } - -pub fn hash_of_binary(data: impl AsRef<[u8]>) -> String { - let mut hasher = Sha256::default(); - hasher.update(data.as_ref()); - let hash = hasher.finalize(); - hex::encode(&hash[..]) -} diff --git a/lib/wasi/src/os/command/mod.rs b/lib/wasi/src/os/command/mod.rs index 32abb2f0594..d8c65a5833a 100644 --- a/lib/wasi/src/os/command/mod.rs +++ b/lib/wasi/src/os/command/mod.rs @@ -5,9 +5,7 @@ use std::{collections::HashMap, sync::Arc}; use wasmer::{FunctionEnvMut, Store}; use wasmer_wasix_types::wasi::Errno; -use crate::{ - syscalls::stderr_write, VirtualBusError, WasiEnv, WasiRuntime, -}; +use crate::{syscalls::stderr_write, VirtualBusError, WasiEnv, WasiRuntime}; use super::task::{OwnedTaskStatus, TaskJoinHandle, TaskStatus}; diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index 686c33272f4..2b3c5f194c7 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -16,7 +16,7 @@ use crate::{ http::DynHttpClient, os::TtyBridge, runtime::{ - module_cache::{ModuleCache, SharedCache}, + module_cache::ModuleCache, resolver::{PackageResolver, RegistryResolver}, }, WasiTtyState, diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs index e68a86c9ff0..43f274224fa 100644 --- a/lib/wasi/src/runtime/module_cache/and_then.rs +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -1,6 +1,6 @@ use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, ModuleCache}; +use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; /// A [`ModuleCache`] combinator which will try operations on one cache /// and fall back to a secondary cache if they fail. @@ -45,7 +45,7 @@ where Primary: ModuleCache + Send + Sync, Secondary: ModuleCache + Send + Sync, { - async fn load(&self, key: &str, engine: &Engine) -> Result { + async fn load(&self, key: Key, engine: &Engine) -> Result { let primary_error = match self.primary.load(key, engine).await { Ok(m) => return Ok(m), Err(e) => e, @@ -56,7 +56,7 @@ where // cache too. if let Err(e) = self.primary.save(key, &m).await { tracing::warn!( - key, + %key, error = &e as &dyn std::error::Error, "Unable to save a module to the primary cache", ); @@ -68,10 +68,10 @@ where Err(primary_error) } - async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { futures::try_join!( - self.primary.save(key, module,), - self.secondary.save(key, module,) + self.primary.save(key, module), + self.secondary.save(key, module) )?; Ok(()) } diff --git a/lib/wasi/src/runtime/module_cache/disabled.rs b/lib/wasi/src/runtime/module_cache/disabled.rs index c1f25a049a2..c0bc3b0a3d0 100644 --- a/lib/wasi/src/runtime/module_cache/disabled.rs +++ b/lib/wasi/src/runtime/module_cache/disabled.rs @@ -1,6 +1,6 @@ use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, ModuleCache}; +use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; /// A cache that always fails. #[derive(Debug, Copy, Clone, PartialEq, Default)] @@ -8,11 +8,11 @@ pub(crate) struct Disabled; #[async_trait::async_trait] impl ModuleCache for Disabled { - async fn load(&self, _key: &str, _engine: &Engine) -> Result { + async fn load(&self, _key: Key, _engine: &Engine) -> Result { Err(CacheError::NotFound) } - async fn save(&self, _key: &str, _module: &Module) -> Result<(), CacheError> { + async fn save(&self, _key: Key, _module: &Module) -> Result<(), CacheError> { Err(CacheError::NotFound) } } diff --git a/lib/wasi/src/runtime/module_cache/mod.rs b/lib/wasi/src/runtime/module_cache/mod.rs index b4a36fa6e49..1b7e95254cf 100644 --- a/lib/wasi/src/runtime/module_cache/mod.rs +++ b/lib/wasi/src/runtime/module_cache/mod.rs @@ -10,7 +10,7 @@ pub use self::{ on_disk::OnDiskCache, shared::SharedCache, thread_local::ThreadLocalCache, - types::{CacheError, ModuleCache}, + types::{CacheError, Key, ModuleCache}, }; pub(crate) use self::disabled::Disabled; @@ -20,10 +20,13 @@ pub(crate) use self::disabled::Disabled; /// /// # Platform-specific Notes /// -/// This will use the [`ThreadLocalCache`] when running in the browser because -/// threads are run in separate workers. If you wish to share compiled modules -/// between threads, you will need to use a custom [`ModuleCache`] -/// implementation. +/// This will use the [`ThreadLocalCache`] when running in the browser. Each +/// thread lives in a separate worker, so sharing compiled modules in the +/// browser requires using a custom [`ModuleCache`] built on top of +/// [`postMessage()`][pm] and [`SharedArrayBuffer`][sab]. +/// +/// [pm]: https://developer.mozilla.org/en-US/docs/Web/API/Worker/postMessage +/// [sab]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer pub fn in_memory() -> impl ModuleCache + Send + Sync { cfg_if::cfg_if! { if #[cfg(feature = "js")] { diff --git a/lib/wasi/src/runtime/module_cache/on_disk.rs b/lib/wasi/src/runtime/module_cache/on_disk.rs index 6311e9aa93e..07415dad925 100644 --- a/lib/wasi/src/runtime/module_cache/on_disk.rs +++ b/lib/wasi/src/runtime/module_cache/on_disk.rs @@ -2,7 +2,7 @@ use std::path::{Path, PathBuf}; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, ModuleCache}; +use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; /// A cache that saves modules to a folder on disk using /// [`Module::serialize()`]. @@ -22,17 +22,14 @@ impl OnDiskCache { &self.cache_dir } - fn path(&self, key: &str) -> PathBuf { - let illegal_path_characters = ['/', '\\', ':', '.']; - let sanitized_key = key.replace(illegal_path_characters, "_"); - - self.cache_dir.join(sanitized_key).with_extension("bin") + fn path(&self, key: Key) -> PathBuf { + self.cache_dir.join(key.to_string()).with_extension("bin") } } #[async_trait::async_trait] impl ModuleCache for OnDiskCache { - async fn load(&self, key: &str, engine: &Engine) -> Result { + async fn load(&self, key: Key, engine: &Engine) -> Result { let path = self.path(key); // FIXME: Use spawn_blocking() to avoid blocking the thread @@ -40,7 +37,7 @@ impl ModuleCache for OnDiskCache { .map_err(|e| deserialize_error(e, path)) } - async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { let path = self.path(key); // FIXME: Use spawn_blocking() to avoid blocking the thread @@ -110,11 +107,15 @@ mod tests { let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); let cache = OnDiskCache::new(temp.path()); - let key = "wat"; + let key = Key::new([0; 32]); cache.save(key, &module).await.unwrap(); - assert!(temp.path().join(key).with_extension("bin").exists()); + assert!(temp + .path() + .join(key.to_string()) + .with_extension("bin") + .exists()); } #[tokio::test] @@ -125,7 +126,7 @@ mod tests { let cache_dir = temp.path().join("this").join("doesn't").join("exist"); assert!(!cache_dir.exists()); let cache = OnDiskCache::new(&cache_dir); - let key = "wat"; + let key = Key::new([0; 32]); cache.save(key, &module).await.unwrap(); @@ -136,7 +137,7 @@ mod tests { async fn missing_file() { let temp = TempDir::new().unwrap(); let engine = Engine::default(); - let key = "wat"; + let key = Key::new([0; 32]); let cache = OnDiskCache::new(temp.path()); let err = cache.load(key, &engine).await.unwrap_err(); @@ -149,9 +150,9 @@ mod tests { let temp = TempDir::new().unwrap(); let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); - let key = "wat"; + let key = Key::new([0; 32]); module - .serialize_to_file(temp.path().join(key).with_extension("bin")) + .serialize_to_file(temp.path().join(key.to_string()).with_extension("bin")) .unwrap(); let cache = OnDiskCache::new(temp.path()); diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index 838fcf96e62..52f2a876075 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -1,12 +1,12 @@ use dashmap::DashMap; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, ModuleCache}; +use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; -/// A [`ModuleCache`] based on a [DashMap]<[String], [Module]>. +/// A [`ModuleCache`] based on a [DashMap]<[Key], [Module]>. #[derive(Debug, Default, Clone)] pub struct SharedCache { - modules: DashMap, + modules: DashMap, } impl SharedCache { @@ -17,15 +17,15 @@ impl SharedCache { #[async_trait::async_trait] impl ModuleCache for SharedCache { - async fn load(&self, key: &str, _engine: &Engine) -> Result { + async fn load(&self, key: Key, _engine: &Engine) -> Result { self.modules - .get(key) + .get(&key) .map(|m| m.value().clone()) .ok_or(CacheError::NotFound) } - async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { - self.modules.insert(key.to_string(), module.clone()); + async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { + self.modules.insert(key, module.clone()); Ok(()) } diff --git a/lib/wasi/src/runtime/module_cache/thread_local.rs b/lib/wasi/src/runtime/module_cache/thread_local.rs index 8d1809e7f7a..959ff039c9c 100644 --- a/lib/wasi/src/runtime/module_cache/thread_local.rs +++ b/lib/wasi/src/runtime/module_cache/thread_local.rs @@ -2,10 +2,10 @@ use std::{cell::RefCell, collections::HashMap}; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, ModuleCache}; +use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; std::thread_local! { - static CACHED_MODULES: RefCell> + static CACHED_MODULES: RefCell> = RefCell::new(HashMap::new()); } @@ -15,22 +15,22 @@ std::thread_local! { pub struct ThreadLocalCache {} impl ThreadLocalCache { - fn lookup(&self, key: &str) -> Option { - CACHED_MODULES.with(|m| m.borrow().get(key).cloned()) + fn lookup(&self, key: Key) -> Option { + CACHED_MODULES.with(|m| m.borrow().get(&key).cloned()) } - fn insert(&self, key: &str, module: &Module) { - CACHED_MODULES.with(|m| m.borrow_mut().insert(key.to_string(), module.clone())); + fn insert(&self, key: Key, module: &Module) { + CACHED_MODULES.with(|m| m.borrow_mut().insert(key, module.clone())); } } #[async_trait::async_trait] impl ModuleCache for ThreadLocalCache { - async fn load(&self, key: &str, _engine: &Engine) -> Result { + async fn load(&self, key: Key, _engine: &Engine) -> Result { self.lookup(key).ok_or(CacheError::NotFound) } - async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { self.insert(key, module); Ok(()) } diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index c739a42b379..cfa55ad9d99 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -1,5 +1,9 @@ -use std::{fmt::Debug, ops::Deref}; +use std::{ + fmt::{self, Debug, Display, Formatter}, + ops::Deref, +}; +use sha2::{Digest, Sha256}; use wasmer::{Engine, Module}; use crate::runtime::module_cache::AndThen; @@ -16,9 +20,9 @@ use crate::runtime::module_cache::AndThen; /// their caching strategy accordingly. #[async_trait::async_trait] pub trait ModuleCache: Debug { - async fn load(&self, key: &str, engine: &Engine) -> Result; + async fn load(&self, key: Key, engine: &Engine) -> Result; - async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError>; + async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError>; /// Chain a second cache onto this one. /// @@ -49,11 +53,11 @@ where D: Deref + Debug + Send + Sync, C: ModuleCache + Send + Sync + ?Sized, { - async fn load(&self, key: &str, engine: &Engine) -> Result { + async fn load(&self, key: Key, engine: &Engine) -> Result { (**self).load(key, engine).await } - async fn save(&self, key: &str, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { (**self).save(key, module).await } } @@ -67,6 +71,50 @@ pub enum CacheError { Other(Box), } +/// A 256-bit key used for caching. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Key([u8; 32]); + +impl Key { + pub fn new(key: [u8; 32]) -> Self { + Key(key) + } + + /// Generate a new [`Key`] based on the SHA-256 hash of some bytes. + pub fn sha256(data: impl AsRef<[u8]>) -> Self { + let mut hasher = Sha256::default(); + hasher.update(data); + Key::new(hasher.finalize().into()) + } + + /// Generate a new [`Key`] which combines this key with the hash of some + /// extra data. + /// + /// If combining a large amount of data, you probably want to hash with + /// [`Sha256`] directly. + pub fn combined_with(self, other_data: impl AsRef<[u8]>) -> Self { + let mut hasher = Sha256::default(); + hasher.update(self.0); + hasher.update(other_data); + Key::new(hasher.finalize().into()) + } + + /// Get the raw bytes for. + pub fn as_bytes(self) -> [u8; 32] { + self.0 + } +} + +impl Display for Key { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + for byte in self.0 { + write!(f, "{byte:02X}")?; + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -75,4 +123,20 @@ mod tests { fn is_object_safe() { let _: Option> = None; } + + #[test] + fn key_is_displayed_as_hex() { + let key = Key::new([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, + 0x1c, 0x1d, 0x1e, 0x1f, + ]); + + let repr = key.to_string(); + + assert_eq!( + repr, + "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F" + ); + } } diff --git a/lib/wasi/src/runtime/resolver/cache.rs b/lib/wasi/src/runtime/resolver/cache.rs index f7cd377c9e5..f0b41777a32 100644 --- a/lib/wasi/src/runtime/resolver/cache.rs +++ b/lib/wasi/src/runtime/resolver/cache.rs @@ -92,6 +92,8 @@ where mod tests { use std::sync::{Arc, Mutex}; + use once_cell::sync::OnceCell; + use super::*; #[derive(Debug, Default)] @@ -117,7 +119,7 @@ mod tests { version: version.parse().unwrap(), when_cached: None, entry: None, - hash: Arc::default(), + hash: OnceCell::new(), webc_fs: None, commands: Arc::default(), uses: Vec::new(), diff --git a/lib/wasi/src/wapm/mod.rs b/lib/wasi/src/wapm/mod.rs index 4c3e5d5959b..22f2d8cb900 100644 --- a/lib/wasi/src/wapm/mod.rs +++ b/lib/wasi/src/wapm/mod.rs @@ -1,8 +1,9 @@ use anyhow::{bail, Context}; +use once_cell::sync::OnceCell; use std::{ collections::HashMap, path::Path, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, RwLock}, }; use url::Url; use virtual_fs::{FileSystem, WebcVolumeFileSystem}; @@ -284,7 +285,7 @@ fn parse_webc_v2(webc: &Container) -> Result { .unwrap() as u128, ), entry: entry.map(Into::into), - hash: Arc::new(Mutex::new(None)), + hash: OnceCell::new(), webc_fs: Some(Arc::new(webc_fs)), commands: Arc::new(RwLock::new(commands.into_values().collect())), uses, diff --git a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new b/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new deleted file mode 100644 index eef6e6dd7fe..00000000000 --- a/tests/integration/cli/tests/snapshots/snapshot__snapshot_fork.snap.new +++ /dev/null @@ -1,29 +0,0 @@ ---- -source: tests/integration/cli/tests/snapshot.rs -assertion_line: 714 -expression: snapshot ---- -{ - "spec": { - "name": "snapshot::test_snapshot_fork", - "use_packages": [ - "sharrattj/coreutils" - ], - "include_webcs": [ - { - "name": "sharrattj/coreutils@1.0.16" - } - ], - "cli_args": [], - "debug_output": false, - "enable_threads": true, - "enable_network": false - }, - "result": { - "Success": { - "stdout": "Parent has x = 0\nChild has x = 2\nParent memory is good\nChild memory is good\nChild(2) exited with 3\n", - "stderr": "", - "exit_code": 0 - } - } -} From f25b3f171af7d29376f5df55061f09eb3e118069 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Fri, 5 May 2023 14:26:32 +0800 Subject: [PATCH 10/18] Fixed a bunch of broken doc links --- lib/api/src/externals/function.rs | 2 +- lib/compiler/src/engine/inner.rs | 2 +- lib/types/src/stack/frame.rs | 10 +++------- lib/vm/src/instance/mod.rs | 2 +- lib/vm/src/vmcontext.rs | 2 +- lib/wasi/src/bin_factory/binary_package.rs | 5 +++-- lib/wasi/src/fs/fd.rs | 6 +++--- lib/wasi/src/lib.rs | 4 +--- lib/wasi/src/os/task/thread.rs | 6 +++--- lib/wasi/src/runtime/module_cache/and_then.rs | 2 +- lib/wasi/src/runtime/module_cache/types.rs | 9 ++++----- lib/wasi/src/runtime/task_manager/tokio.rs | 2 +- lib/wasi/src/state/builder.rs | 2 +- 13 files changed, 24 insertions(+), 30 deletions(-) diff --git a/lib/api/src/externals/function.rs b/lib/api/src/externals/function.rs index 734a3f8a866..48ac7eff2a8 100644 --- a/lib/api/src/externals/function.rs +++ b/lib/api/src/externals/function.rs @@ -60,7 +60,7 @@ mod private { //! Sealing the HostFunctionKind because it shouldn't be implemented //! by any type outside. //! See: - //! https://rust-lang.github.io/api-guidelines/future-proofing.html#c-sealed + //! pub trait HostFunctionKindSealed {} impl HostFunctionKindSealed for super::WithEnv {} impl HostFunctionKindSealed for super::WithoutEnv {} diff --git a/lib/compiler/src/engine/inner.rs b/lib/compiler/src/engine/inner.rs index 3b89c1c7662..5424966fdd1 100644 --- a/lib/compiler/src/engine/inner.rs +++ b/lib/compiler/src/engine/inner.rs @@ -220,7 +220,7 @@ impl Engine { /// /// # Safety /// - /// See [`crate::Module::deserialize_from_file`]. + /// See [`Artifact::deserialize`]. #[cfg(not(target_arch = "wasm32"))] pub unsafe fn deserialize_from_file( &self, diff --git a/lib/types/src/stack/frame.rs b/lib/types/src/stack/frame.rs index e6cb53062a4..989b0fc86ea 100644 --- a/lib/types/src/stack/frame.rs +++ b/lib/types/src/stack/frame.rs @@ -1,13 +1,9 @@ use crate::SourceLoc; -/// Description of a frame in a backtrace for a [`RuntimeError`](crate::RuntimeError). +/// Description of a frame in a backtrace. /// -/// Whenever a WebAssembly trap occurs an instance of [`RuntimeError`] -/// is created. Each [`RuntimeError`] has a backtrace of the -/// WebAssembly frames that led to the trap, and each frame is -/// described by this structure. -/// -/// [`RuntimeError`]: crate::RuntimeError +/// Each runtime error includes a backtrace of the WebAssembly frames that led +/// to the trap, and each frame is described by this structure. #[derive(Debug, Clone)] pub struct FrameInfo { /// The name of the module diff --git a/lib/vm/src/instance/mod.rs b/lib/vm/src/instance/mod.rs index 0909e6927f5..52b0a9e0f00 100644 --- a/lib/vm/src/instance/mod.rs +++ b/lib/vm/src/instance/mod.rs @@ -989,7 +989,7 @@ impl Drop for VMInstance { } impl VMInstance { - /// Create a new `VMInstance` pointing at a new [`InstanceRef`]. + /// Create a new `VMInstance` pointing at a new [`Instance`]. /// /// # Safety /// diff --git a/lib/vm/src/vmcontext.rs b/lib/vm/src/vmcontext.rs index f87df89c471..9e3fa240114 100644 --- a/lib/vm/src/vmcontext.rs +++ b/lib/vm/src/vmcontext.rs @@ -272,7 +272,7 @@ pub struct VMGlobalImport { /// # Safety /// This data is safe to share between threads because it's plain data that /// is the user's responsibility to synchronize. Additionally, all operations -/// on `from` are thread-safe through the use of a mutex in [`Global`]. +/// on `from` are thread-safe through the use of a mutex in [`VMGlobal`]. unsafe impl Send for VMGlobalImport {} /// # Safety /// This data is safe to share between threads because it's plain data that diff --git a/lib/wasi/src/bin_factory/binary_package.rs b/lib/wasi/src/bin_factory/binary_package.rs index 7924c4a69c4..8e57e794014 100644 --- a/lib/wasi/src/bin_factory/binary_package.rs +++ b/lib/wasi/src/bin_factory/binary_package.rs @@ -45,8 +45,9 @@ impl BinaryPackageCommand { /// A WebAssembly package that has been loaded into memory. /// -/// You can crate a [`BinaryPackage`] using [`crate::bin_factory::ModuleCache`] -/// or [`crate::wapm::parse_static_webc()`]. +/// You can crate a [`BinaryPackage`] using a +/// [`crate::runtime::resolver::PackageResolver`] or +/// [`crate::wapm::parse_static_webc()`]. #[derive(Derivative, Clone)] #[derivative(Debug)] pub struct BinaryPackage { diff --git a/lib/wasi/src/fs/fd.rs b/lib/wasi/src/fs/fd.rs index 94ea01eac8a..1eb147745e8 100644 --- a/lib/wasi/src/fs/fd.rs +++ b/lib/wasi/src/fs/fd.rs @@ -23,7 +23,7 @@ pub struct Fd { pub offset: Arc, /// Flags that determine how the [`Fd`] can be used. /// - /// Used when reopening a [`VirtualFile`] during [`WasiState`] deserialization. + /// Used when reopening a [`VirtualFile`] during deserialization. pub open_flags: u16, pub inode: InodeGuard, pub is_stdio: bool, @@ -40,12 +40,12 @@ impl Fd { /// This [`Fd`] will delete everything before writing. Note that truncate /// permissions require the write permission. /// - /// This permission is currently unused when deserializing [`WasiState`]. + /// This permission is currently unused when deserializing. pub const TRUNCATE: u16 = 8; /// This [`Fd`] may create a file before writing to it. Note that create /// permissions require write permissions. /// - /// This permission is currently unused when deserializing [`WasiState`]. + /// This permission is currently unused when deserializing. pub const CREATE: u16 = 16; } diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index f14d32b272c..90f194c844f 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -349,9 +349,7 @@ pub fn current_caller_id() -> WasiCallingId { .into() } -/// Create an [`Imports`] with an existing [`WasiEnv`]. `WasiEnv` -/// needs a [`WasiState`], that can be constructed from a -/// [`WasiEnvBuilder`](state::WasiEnvBuilder). +/// Create an [`Imports`] with an existing [`WasiEnv`]. pub fn generate_import_object_from_env( store: &mut impl AsStoreMut, ctx: &FunctionEnv, diff --git a/lib/wasi/src/os/task/thread.rs b/lib/wasi/src/os/task/thread.rs index 4a0d265d659..99a106650c6 100644 --- a/lib/wasi/src/os/task/thread.rs +++ b/lib/wasi/src/os/task/thread.rs @@ -99,9 +99,9 @@ pub struct WasiThread { /// A guard that ensures a thread is marked as terminated when dropped. /// /// Normally the thread result should be manually registered with -/// [`Thread::set_status_running`] or [`Thread::set_status_finished`], but -/// this guard can ensure that the thread is marked as terminated even if this -/// is forgotten or a panic occurs. +/// [`WasiThread::set_status_running`] or [`WasiThread::set_status_finished`], +/// but this guard can ensure that the thread is marked as terminated even if +/// this is forgotten or a panic occurs. pub struct WasiThreadRunGuard { pub thread: WasiThread, } diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs index 43f274224fa..bcfb83eb8ad 100644 --- a/lib/wasi/src/runtime/module_cache/and_then.rs +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -5,7 +5,7 @@ use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; /// A [`ModuleCache`] combinator which will try operations on one cache /// and fall back to a secondary cache if they fail. /// -/// Constructed via [`CompiledModuleCache::and_then()`]. +/// Constructed via [`ModuleCache::and_then()`]. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct AndThen { primary: Primary, diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index cfa55ad9d99..8685f6b717c 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -15,8 +15,8 @@ use crate::runtime::module_cache::AndThen; /// Implementations can assume that cache keys are unique and that using the /// same key to load or save will always result in the "same" module. /// -/// Implementations can also assume that [`CompiledModuleCache::load()`] will -/// be called more often than [`CompiledModuleCache::save()`] and optimise +/// Implementations can also assume that [`ModuleCache::load()`] will +/// be called more often than [`ModuleCache::save()`] and optimise /// their caching strategy accordingly. #[async_trait::async_trait] pub trait ModuleCache: Debug { @@ -31,11 +31,10 @@ pub trait ModuleCache: Debug { /// /// ```rust /// use wasmer_wasix::runtime::module_cache::{ - /// CompiledModuleCache, ThreadLocalCache, OnDiskCache, SharedCache, + /// ModuleCache, ThreadLocalCache, OnDiskCache, SharedCache, /// }; /// - /// let cache = ThreadLocalCache::default() - /// .and_then(SharedCache::default()) + /// let cache = SharedCache::default() /// .and_then(OnDiskCache::new("~/.local/cache")); /// ``` fn and_then(self, other: C) -> AndThen diff --git a/lib/wasi/src/runtime/task_manager/tokio.rs b/lib/wasi/src/runtime/task_manager/tokio.rs index b6ab24d7d7b..dade0380f29 100644 --- a/lib/wasi/src/runtime/task_manager/tokio.rs +++ b/lib/wasi/src/runtime/task_manager/tokio.rs @@ -43,7 +43,7 @@ impl TokioTaskManager { Ok(()) } - /// Shared tokio [`Runtime`] that is used by default. + /// Shared tokio [`VirtualTaskManager`] that is used by default. /// /// This exists because a tokio runtime is heavy, and there should not be many /// independent ones in a process. diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs index ba84378482f..1e5ae200848 100644 --- a/lib/wasi/src/state/builder.rs +++ b/lib/wasi/src/state/builder.rs @@ -757,7 +757,7 @@ impl WasiEnvBuilder { } /// Consumes the [`WasiEnvBuilder`] and produces a [`WasiEnvInit`], which - /// can be used to construct a new [`WasiEnv`] with [`WasiEnv::new`]. + /// can be used to construct a new [`WasiEnv`]. /// /// Returns the error from `WasiFs::new` if there's an error // FIXME: use a proper custom error type From 87f684ae681b7e46bb7a18f4fd70e19c28de1fba Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Fri, 5 May 2023 16:42:58 +0800 Subject: [PATCH 11/18] Rewrote the FileSystem cache to take @theduke's comments into account --- lib/types/src/serialize.rs | 2 +- .../src/runtime/module_cache/filesystem.rs | 231 ++++++++++++++++++ lib/wasi/src/runtime/module_cache/mod.rs | 4 +- lib/wasi/src/runtime/module_cache/on_disk.rs | 167 ------------- lib/wasi/src/runtime/module_cache/types.rs | 27 +- 5 files changed, 259 insertions(+), 172 deletions(-) create mode 100644 lib/wasi/src/runtime/module_cache/filesystem.rs delete mode 100644 lib/wasi/src/runtime/module_cache/on_disk.rs diff --git a/lib/types/src/serialize.rs b/lib/types/src/serialize.rs index 2c294dc2718..4528b9b1c2a 100644 --- a/lib/types/src/serialize.rs +++ b/lib/types/src/serialize.rs @@ -207,7 +207,7 @@ pub struct MetadataHeader { impl MetadataHeader { /// Current ABI version. Increment this any time breaking changes are made /// to the format of the serialized data. - const CURRENT_VERSION: u32 = 4; + pub const CURRENT_VERSION: u32 = 4; /// Magic number to identify wasmer metadata. const MAGIC: [u8; 8] = *b"WASMER\0\0"; diff --git a/lib/wasi/src/runtime/module_cache/filesystem.rs b/lib/wasi/src/runtime/module_cache/filesystem.rs new file mode 100644 index 00000000000..65511f9483a --- /dev/null +++ b/lib/wasi/src/runtime/module_cache/filesystem.rs @@ -0,0 +1,231 @@ +use std::{ + fs::File, + path::{Path, PathBuf}, + sync::atomic::{AtomicU64, Ordering}, +}; + +use wasmer::{Engine, Module}; + +use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; + +/// A cache that saves modules to a folder on the host filesystem using +/// [`Module::serialize()`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FileSystemCache { + cache_dir: PathBuf, +} + +impl FileSystemCache { + pub fn new(cache_dir: impl Into) -> Self { + FileSystemCache { + cache_dir: cache_dir.into(), + } + } + + pub fn cache_dir(&self) -> &Path { + &self.cache_dir + } + + fn path(&self, key: Key) -> PathBuf { + let artifact_version = wasmer_types::MetadataHeader::CURRENT_VERSION; + let dir = format!("artifact-v{artifact_version}"); + self.cache_dir + .join(dir) + .join(key.to_string()) + .with_extension("bin") + } +} + +#[async_trait::async_trait] +impl ModuleCache for FileSystemCache { + async fn load(&self, key: Key, engine: &Engine) -> Result { + let path = self.path(key); + + // FIXME: This will all block the thread at the moment. Ideally, + // deserializing and uncompressing would happen on a thread pool in the + // background. + + let uncompressed = read_compressed(&path)?; + + match Module::deserialize_checked(&engine, &uncompressed) { + Ok(m) => Ok(m), + Err(e) => { + tracing::debug!( + %key, + path=%path.display(), + error=&e as &dyn std::error::Error, + "Deleting the cache file because the artifact couldn't be deserialized", + ); + + if let Err(e) = std::fs::remove_file(&path) { + tracing::warn!( + %key, + path=%path.display(), + error=&e as &dyn std::error::Error, + "Unable to remove the corrupted cache file", + ); + } + + Err(CacheError::Deserialize(e)) + } + } + } + + async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { + let path = self.path(key); + + // FIXME: This will all block the thread at the moment. Ideally, + // serializing and compressing would happen on a thread pool in the + // background. + + if let Some(parent) = path.parent() { + if let Err(e) = std::fs::create_dir_all(parent) { + tracing::warn!( + dir=%parent.display(), + error=&e as &dyn std::error::Error, + "Unable to create the cache directory", + ); + } + } + + // Note: We'll first save to a temporary file in the same folder, then + // rename it when we are done serializing. Try to use a unique extension + // just in case we have concurrent saves of the same module. + static UNIQUE_ID: AtomicU64 = AtomicU64::new(0); + let extension = format!("tmp{}", UNIQUE_ID.fetch_add(1, Ordering::Relaxed)); + + let tmp = path.with_extension(extension); + + let serialized = module.serialize()?; + if let Err(e) = save_compressed(&tmp, &serialized) { + if let Err(e) = std::fs::remove_file(&tmp) { + tracing::warn!( + path=%path.display(), + key=%key, + error=&e as &dyn std::error::Error, + "Unable to remove the temporary file", + ); + } + + return Err(CacheError::Write { path, error: e }); + } + + std::fs::rename(&tmp, &path).map_err(|error| CacheError::Write { path, error }) + } +} + +fn save_compressed(path: &Path, data: &[u8]) -> Result<(), std::io::Error> { + let mut f = File::create(path)?; + let mut encoder = weezl::encode::Encoder::new(weezl::BitOrder::Msb, 8); + encoder + .into_stream(&mut f) + .encode_all(std::io::Cursor::new(data)) + .status?; + f.sync_all()?; + + Ok(()) +} + +fn read_compressed(path: &Path) -> Result, CacheError> { + let compressed = match std::fs::read(path) { + Ok(bytes) => bytes, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + return Err(CacheError::NotFound); + } + Err(error) => { + return Err(CacheError::Read { + path: path.to_path_buf(), + error, + }); + } + }; + + let mut uncompressed = Vec::new(); + let mut decoder = weezl::decode::Decoder::new(weezl::BitOrder::Msb, 8); + decoder + .into_vec(&mut uncompressed) + .decode_all(&compressed) + .status + .map_err(CacheError::other)?; + + Ok(uncompressed) +} + +#[cfg(test)] +mod tests { + use tempfile::TempDir; + + use super::*; + + const ADD_WAT: &[u8] = br#"( + module + (func + (export "add") + (param $x i64) + (param $y i64) + (result i64) + (i64.add (local.get $x) (local.get $y))) + )"#; + + #[tokio::test] + async fn save_to_disk() { + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let cache = FileSystemCache::new(temp.path()); + let key = Key::new([0; 32]); + let expected_path = cache.path(key); + + cache.save(key, &module).await.unwrap(); + + assert!(expected_path.exists()); + } + + #[tokio::test] + async fn create_cache_dir_automatically() { + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let cache_dir = temp.path().join("this").join("doesn't").join("exist"); + assert!(!cache_dir.exists()); + let cache = FileSystemCache::new(&cache_dir); + let key = Key::new([0; 32]); + + cache.save(key, &module).await.unwrap(); + + assert!(cache_dir.is_dir()); + } + + #[tokio::test] + async fn missing_file() { + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let key = Key::new([0; 32]); + let cache = FileSystemCache::new(temp.path()); + + let err = cache.load(key, &engine).await.unwrap_err(); + + assert!(matches!(err, CacheError::NotFound)); + } + + #[tokio::test] + async fn load_from_disk() { + let temp = TempDir::new().unwrap(); + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let key = Key::new([0; 32]); + let cache = FileSystemCache::new(temp.path()); + let expected_path = cache.path(key); + std::fs::create_dir_all(expected_path.parent().unwrap()).unwrap(); + let serialized = module.serialize().unwrap(); + save_compressed(&expected_path, &serialized).unwrap(); + + let module = cache.load(key, &engine).await.unwrap(); + + let exports: Vec<_> = module + .exports() + .map(|export| export.name().to_string()) + .collect(); + assert_eq!(exports, ["add"]); + } +} diff --git a/lib/wasi/src/runtime/module_cache/mod.rs b/lib/wasi/src/runtime/module_cache/mod.rs index 1b7e95254cf..791734c5fa0 100644 --- a/lib/wasi/src/runtime/module_cache/mod.rs +++ b/lib/wasi/src/runtime/module_cache/mod.rs @@ -1,13 +1,13 @@ mod and_then; mod disabled; -mod on_disk; +mod filesystem; mod shared; mod thread_local; mod types; pub use self::{ and_then::AndThen, - on_disk::OnDiskCache, + filesystem::FileSystemCache, shared::SharedCache, thread_local::ThreadLocalCache, types::{CacheError, Key, ModuleCache}, diff --git a/lib/wasi/src/runtime/module_cache/on_disk.rs b/lib/wasi/src/runtime/module_cache/on_disk.rs deleted file mode 100644 index 07415dad925..00000000000 --- a/lib/wasi/src/runtime/module_cache/on_disk.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::path::{Path, PathBuf}; - -use wasmer::{Engine, Module}; - -use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; - -/// A cache that saves modules to a folder on disk using -/// [`Module::serialize()`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct OnDiskCache { - cache_dir: PathBuf, -} - -impl OnDiskCache { - pub fn new(cache_dir: impl Into) -> Self { - OnDiskCache { - cache_dir: cache_dir.into(), - } - } - - pub fn cache_dir(&self) -> &Path { - &self.cache_dir - } - - fn path(&self, key: Key) -> PathBuf { - self.cache_dir.join(key.to_string()).with_extension("bin") - } -} - -#[async_trait::async_trait] -impl ModuleCache for OnDiskCache { - async fn load(&self, key: Key, engine: &Engine) -> Result { - let path = self.path(key); - - // FIXME: Use spawn_blocking() to avoid blocking the thread - Module::deserialize_from_file_checked(&engine, &path) - .map_err(|e| deserialize_error(e, path)) - } - - async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { - let path = self.path(key); - - // FIXME: Use spawn_blocking() to avoid blocking the thread - - if let Some(parent) = path.parent() { - if let Err(e) = std::fs::create_dir_all(parent) { - tracing::warn!( - dir=%parent.display(), - error=&e as &dyn std::error::Error, - "Unable to create the cache dir", - ); - } - } - - // PERF: We can reduce disk usage by using the weezl crate to - // LZW-encode the serialized module. - module - .serialize_to_file(&path) - .map_err(|e| CacheError::Other(Box::new(SerializeError { path, inner: e }))) - } -} - -#[derive(Debug, thiserror::Error)] -#[error("Unable to save to \"{}\"", path.display())] -struct SerializeError { - path: PathBuf, - #[source] - inner: wasmer::SerializeError, -} - -#[derive(Debug, thiserror::Error)] -#[error("Unable to deserialize from \"{}\"", path.display())] -struct DeserializeError { - path: PathBuf, - #[source] - inner: wasmer::DeserializeError, -} - -fn deserialize_error(e: wasmer::DeserializeError, path: PathBuf) -> CacheError { - match e { - wasmer::DeserializeError::Io(io) if io.kind() == std::io::ErrorKind::NotFound => { - CacheError::NotFound - } - other => CacheError::Other(Box::new(DeserializeError { path, inner: other })), - } -} - -#[cfg(test)] -mod tests { - use tempfile::TempDir; - - use super::*; - - const ADD_WAT: &[u8] = br#"( - module - (func - (export "add") - (param $x i64) - (param $y i64) - (result i64) - (i64.add (local.get $x) (local.get $y))) - )"#; - - #[tokio::test] - async fn save_to_disk() { - let temp = TempDir::new().unwrap(); - let engine = Engine::default(); - let module = Module::new(&engine, ADD_WAT).unwrap(); - let cache = OnDiskCache::new(temp.path()); - let key = Key::new([0; 32]); - - cache.save(key, &module).await.unwrap(); - - assert!(temp - .path() - .join(key.to_string()) - .with_extension("bin") - .exists()); - } - - #[tokio::test] - async fn create_cache_dir_automatically() { - let temp = TempDir::new().unwrap(); - let engine = Engine::default(); - let module = Module::new(&engine, ADD_WAT).unwrap(); - let cache_dir = temp.path().join("this").join("doesn't").join("exist"); - assert!(!cache_dir.exists()); - let cache = OnDiskCache::new(&cache_dir); - let key = Key::new([0; 32]); - - cache.save(key, &module).await.unwrap(); - - assert!(cache_dir.is_dir()); - } - - #[tokio::test] - async fn missing_file() { - let temp = TempDir::new().unwrap(); - let engine = Engine::default(); - let key = Key::new([0; 32]); - let cache = OnDiskCache::new(temp.path()); - - let err = cache.load(key, &engine).await.unwrap_err(); - - assert!(matches!(err, CacheError::NotFound)); - } - - #[tokio::test] - async fn load_from_disk() { - let temp = TempDir::new().unwrap(); - let engine = Engine::default(); - let module = Module::new(&engine, ADD_WAT).unwrap(); - let key = Key::new([0; 32]); - module - .serialize_to_file(temp.path().join(key.to_string()).with_extension("bin")) - .unwrap(); - let cache = OnDiskCache::new(temp.path()); - - let module = cache.load(key, &engine).await.unwrap(); - - let exports: Vec<_> = module - .exports() - .map(|export| export.name().to_string()) - .collect(); - assert_eq!(exports, ["add"]); - } -} diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index 8685f6b717c..64724502e73 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -1,6 +1,7 @@ use std::{ fmt::{self, Debug, Display, Formatter}, ops::Deref, + path::PathBuf, }; use sha2::{Digest, Sha256}; @@ -31,11 +32,11 @@ pub trait ModuleCache: Debug { /// /// ```rust /// use wasmer_wasix::runtime::module_cache::{ - /// ModuleCache, ThreadLocalCache, OnDiskCache, SharedCache, + /// ModuleCache, ThreadLocalCache, FileSystemCache, SharedCache, /// }; /// /// let cache = SharedCache::default() - /// .and_then(OnDiskCache::new("~/.local/cache")); + /// .and_then(FileSystemCache::new("~/.local/cache")); /// ``` fn and_then(self, other: C) -> AndThen where @@ -63,6 +64,22 @@ where #[derive(Debug, thiserror::Error)] pub enum CacheError { + #[error("Unable to serialize the module")] + Serialize(#[from] wasmer::SerializeError), + #[error("Unable to deserialize the module")] + Deserialize(#[from] wasmer::DeserializeError), + #[error("Unable to read from \"{}\"", path.display())] + Read { + path: PathBuf, + #[source] + error: std::io::Error, + }, + #[error("Unable to write to \"{}\"", path.display())] + Write { + path: PathBuf, + #[source] + error: std::io::Error, + }, /// The item was not found. #[error("Not found")] NotFound, @@ -70,6 +87,12 @@ pub enum CacheError { Other(Box), } +impl CacheError { + pub fn other(error: impl std::error::Error + Send + Sync + 'static) -> Self { + CacheError::Other(Box::new(error)) + } +} + /// A 256-bit key used for caching. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Key([u8; 32]); From e633eb3e52f0b5a8f27c726ee1ffd52f0070bf7f Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Fri, 5 May 2023 16:52:28 +0800 Subject: [PATCH 12/18] Deleted the Disabled cache and made a ModuleCache compulsory --- lib/wasi/src/runtime/mod.rs | 6 +----- lib/wasi/src/runtime/module_cache/disabled.rs | 18 ------------------ lib/wasi/src/runtime/module_cache/mod.rs | 3 --- 3 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 lib/wasi/src/runtime/module_cache/disabled.rs diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index 2b3c5f194c7..f2edf2f55c9 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -39,11 +39,7 @@ where fn package_resolver(&self) -> Arc; /// A cache for compiled modules. - /// - /// Caching is disabled by default. - fn module_cache(&self) -> Arc { - Arc::new(module_cache::Disabled) - } + fn module_cache(&self) -> Arc; /// Get a [`wasmer::Engine`] for module compilation. fn engine(&self) -> Option { diff --git a/lib/wasi/src/runtime/module_cache/disabled.rs b/lib/wasi/src/runtime/module_cache/disabled.rs deleted file mode 100644 index c0bc3b0a3d0..00000000000 --- a/lib/wasi/src/runtime/module_cache/disabled.rs +++ /dev/null @@ -1,18 +0,0 @@ -use wasmer::{Engine, Module}; - -use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; - -/// A cache that always fails. -#[derive(Debug, Copy, Clone, PartialEq, Default)] -pub(crate) struct Disabled; - -#[async_trait::async_trait] -impl ModuleCache for Disabled { - async fn load(&self, _key: Key, _engine: &Engine) -> Result { - Err(CacheError::NotFound) - } - - async fn save(&self, _key: Key, _module: &Module) -> Result<(), CacheError> { - Err(CacheError::NotFound) - } -} diff --git a/lib/wasi/src/runtime/module_cache/mod.rs b/lib/wasi/src/runtime/module_cache/mod.rs index 791734c5fa0..c80c6e24d58 100644 --- a/lib/wasi/src/runtime/module_cache/mod.rs +++ b/lib/wasi/src/runtime/module_cache/mod.rs @@ -1,5 +1,4 @@ mod and_then; -mod disabled; mod filesystem; mod shared; mod thread_local; @@ -13,8 +12,6 @@ pub use self::{ types::{CacheError, Key, ModuleCache}, }; -pub(crate) use self::disabled::Disabled; - /// Get a [`ModuleCache`] which should be good enough for most in-memory use /// cases. /// From 8828b04b53a5fb18695426edb64fcd8888314db6 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Mon, 8 May 2023 14:16:34 +0800 Subject: [PATCH 13/18] Changed the cache Key's semantics so it is *just* a module hash, and renamed it appropriately --- lib/wasi/src/bin_factory/binary_package.rs | 16 +++---- lib/wasi/src/bin_factory/exec.rs | 5 +-- lib/wasi/src/runtime/module_cache/and_then.rs | 6 +-- .../src/runtime/module_cache/filesystem.rs | 16 +++---- lib/wasi/src/runtime/module_cache/mod.rs | 2 +- lib/wasi/src/runtime/module_cache/shared.rs | 10 ++--- .../src/runtime/module_cache/thread_local.rs | 12 ++--- lib/wasi/src/runtime/module_cache/types.rs | 44 ++++++++----------- 8 files changed, 51 insertions(+), 60 deletions(-) diff --git a/lib/wasi/src/bin_factory/binary_package.rs b/lib/wasi/src/bin_factory/binary_package.rs index 8e57e794014..8a3e05b7cc9 100644 --- a/lib/wasi/src/bin_factory/binary_package.rs +++ b/lib/wasi/src/bin_factory/binary_package.rs @@ -6,7 +6,7 @@ use semver::Version; use virtual_fs::FileSystem; use webc::compat::SharedBytes; -use crate::runtime::module_cache::Key; +use crate::runtime::module_cache::ModuleHash; #[derive(Derivative, Clone)] #[derivative(Debug)] @@ -14,7 +14,7 @@ pub struct BinaryPackageCommand { name: String, #[derivative(Debug = "ignore")] pub(crate) atom: SharedBytes, - hash: OnceCell, + hash: OnceCell, } impl BinaryPackageCommand { @@ -38,8 +38,8 @@ impl BinaryPackageCommand { &self.atom } - pub fn hash(&self) -> &Key { - self.hash.get_or_init(|| Key::sha256(self.atom())) + pub fn hash(&self) -> &ModuleHash { + self.hash.get_or_init(|| ModuleHash::sha256(self.atom())) } } @@ -55,7 +55,7 @@ pub struct BinaryPackage { pub when_cached: Option, #[derivative(Debug = "ignore")] pub entry: Option, - pub hash: OnceCell, + pub hash: OnceCell, pub webc_fs: Option>, pub commands: Arc>>, pub uses: Vec, @@ -65,12 +65,12 @@ pub struct BinaryPackage { } impl BinaryPackage { - pub fn hash(&self) -> Key { + pub fn hash(&self) -> ModuleHash { *self.hash.get_or_init(|| { if let Some(entry) = self.entry.as_ref() { - Key::sha256(entry) + ModuleHash::sha256(entry) } else { - Key::sha256(self.package_name.as_bytes()) + ModuleHash::sha256(self.package_name.as_bytes()) } }) } diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index c29b1ddd73c..4c155a71697 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -24,10 +24,7 @@ pub async fn spawn_exec( env: WasiEnv, runtime: &Arc, ) -> Result { - // The deterministic id for this engine - let compiler = store.engine().deterministic_id(); - - let key = binary.hash().combined_with(compiler); + let key = binary.hash(); let compiled_modules = runtime.module_cache(); let module = compiled_modules.load(key, store.engine()).await.ok(); diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs index bcfb83eb8ad..f0713a934a9 100644 --- a/lib/wasi/src/runtime/module_cache/and_then.rs +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -1,6 +1,6 @@ use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache, ModuleHash}; /// A [`ModuleCache`] combinator which will try operations on one cache /// and fall back to a secondary cache if they fail. @@ -45,7 +45,7 @@ where Primary: ModuleCache + Send + Sync, Secondary: ModuleCache + Send + Sync, { - async fn load(&self, key: Key, engine: &Engine) -> Result { + async fn load(&self, key: ModuleHash, engine: &Engine) -> Result { let primary_error = match self.primary.load(key, engine).await { Ok(m) => return Ok(m), Err(e) => e, @@ -68,7 +68,7 @@ where Err(primary_error) } - async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { futures::try_join!( self.primary.save(key, module), self.secondary.save(key, module) diff --git a/lib/wasi/src/runtime/module_cache/filesystem.rs b/lib/wasi/src/runtime/module_cache/filesystem.rs index 65511f9483a..6af145ac0e2 100644 --- a/lib/wasi/src/runtime/module_cache/filesystem.rs +++ b/lib/wasi/src/runtime/module_cache/filesystem.rs @@ -6,7 +6,7 @@ use std::{ use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache, ModuleHash}; /// A cache that saves modules to a folder on the host filesystem using /// [`Module::serialize()`]. @@ -26,7 +26,7 @@ impl FileSystemCache { &self.cache_dir } - fn path(&self, key: Key) -> PathBuf { + fn path(&self, key: ModuleHash) -> PathBuf { let artifact_version = wasmer_types::MetadataHeader::CURRENT_VERSION; let dir = format!("artifact-v{artifact_version}"); self.cache_dir @@ -38,7 +38,7 @@ impl FileSystemCache { #[async_trait::async_trait] impl ModuleCache for FileSystemCache { - async fn load(&self, key: Key, engine: &Engine) -> Result { + async fn load(&self, key: ModuleHash, engine: &Engine) -> Result { let path = self.path(key); // FIXME: This will all block the thread at the moment. Ideally, @@ -71,7 +71,7 @@ impl ModuleCache for FileSystemCache { } } - async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { let path = self.path(key); // FIXME: This will all block the thread at the moment. Ideally, @@ -173,7 +173,7 @@ mod tests { let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); let cache = FileSystemCache::new(temp.path()); - let key = Key::new([0; 32]); + let key = ModuleHash::new([0; 32]); let expected_path = cache.path(key); cache.save(key, &module).await.unwrap(); @@ -189,7 +189,7 @@ mod tests { let cache_dir = temp.path().join("this").join("doesn't").join("exist"); assert!(!cache_dir.exists()); let cache = FileSystemCache::new(&cache_dir); - let key = Key::new([0; 32]); + let key = ModuleHash::new([0; 32]); cache.save(key, &module).await.unwrap(); @@ -200,7 +200,7 @@ mod tests { async fn missing_file() { let temp = TempDir::new().unwrap(); let engine = Engine::default(); - let key = Key::new([0; 32]); + let key = ModuleHash::new([0; 32]); let cache = FileSystemCache::new(temp.path()); let err = cache.load(key, &engine).await.unwrap_err(); @@ -213,7 +213,7 @@ mod tests { let temp = TempDir::new().unwrap(); let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); - let key = Key::new([0; 32]); + let key = ModuleHash::new([0; 32]); let cache = FileSystemCache::new(temp.path()); let expected_path = cache.path(key); std::fs::create_dir_all(expected_path.parent().unwrap()).unwrap(); diff --git a/lib/wasi/src/runtime/module_cache/mod.rs b/lib/wasi/src/runtime/module_cache/mod.rs index c80c6e24d58..928dd05dc8e 100644 --- a/lib/wasi/src/runtime/module_cache/mod.rs +++ b/lib/wasi/src/runtime/module_cache/mod.rs @@ -9,7 +9,7 @@ pub use self::{ filesystem::FileSystemCache, shared::SharedCache, thread_local::ThreadLocalCache, - types::{CacheError, Key, ModuleCache}, + types::{CacheError, ModuleCache, ModuleHash}, }; /// Get a [`ModuleCache`] which should be good enough for most in-memory use diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index 52f2a876075..73bc0a90735 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -1,12 +1,12 @@ use dashmap::DashMap; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache, ModuleHash}; -/// A [`ModuleCache`] based on a [DashMap]<[Key], [Module]>. +/// A [`ModuleCache`] based on a [DashMap]<[ModuleHash], [Module]>. #[derive(Debug, Default, Clone)] pub struct SharedCache { - modules: DashMap, + modules: DashMap, } impl SharedCache { @@ -17,14 +17,14 @@ impl SharedCache { #[async_trait::async_trait] impl ModuleCache for SharedCache { - async fn load(&self, key: Key, _engine: &Engine) -> Result { + async fn load(&self, key: ModuleHash, _engine: &Engine) -> Result { self.modules .get(&key) .map(|m| m.value().clone()) .ok_or(CacheError::NotFound) } - async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { self.modules.insert(key, module.clone()); Ok(()) diff --git a/lib/wasi/src/runtime/module_cache/thread_local.rs b/lib/wasi/src/runtime/module_cache/thread_local.rs index 959ff039c9c..6937df9e6d6 100644 --- a/lib/wasi/src/runtime/module_cache/thread_local.rs +++ b/lib/wasi/src/runtime/module_cache/thread_local.rs @@ -2,10 +2,10 @@ use std::{cell::RefCell, collections::HashMap}; use wasmer::{Engine, Module}; -use crate::runtime::module_cache::{CacheError, Key, ModuleCache}; +use crate::runtime::module_cache::{CacheError, ModuleCache, ModuleHash}; std::thread_local! { - static CACHED_MODULES: RefCell> + static CACHED_MODULES: RefCell> = RefCell::new(HashMap::new()); } @@ -15,22 +15,22 @@ std::thread_local! { pub struct ThreadLocalCache {} impl ThreadLocalCache { - fn lookup(&self, key: Key) -> Option { + fn lookup(&self, key: ModuleHash) -> Option { CACHED_MODULES.with(|m| m.borrow().get(&key).cloned()) } - fn insert(&self, key: Key, module: &Module) { + fn insert(&self, key: ModuleHash, module: &Module) { CACHED_MODULES.with(|m| m.borrow_mut().insert(key, module.clone())); } } #[async_trait::async_trait] impl ModuleCache for ThreadLocalCache { - async fn load(&self, key: Key, _engine: &Engine) -> Result { + async fn load(&self, key: ModuleHash, _engine: &Engine) -> Result { self.lookup(key).ok_or(CacheError::NotFound) } - async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { self.insert(key, module); Ok(()) } diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index 64724502e73..a55a5015902 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -21,9 +21,9 @@ use crate::runtime::module_cache::AndThen; /// their caching strategy accordingly. #[async_trait::async_trait] pub trait ModuleCache: Debug { - async fn load(&self, key: Key, engine: &Engine) -> Result; + async fn load(&self, key: ModuleHash, engine: &Engine) -> Result; - async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError>; + async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError>; /// Chain a second cache onto this one. /// @@ -53,11 +53,11 @@ where D: Deref + Debug + Send + Sync, C: ModuleCache + Send + Sync + ?Sized, { - async fn load(&self, key: Key, engine: &Engine) -> Result { + async fn load(&self, key: ModuleHash, engine: &Engine) -> Result { (**self).load(key, engine).await } - async fn save(&self, key: Key, module: &Module) -> Result<(), CacheError> { + async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { (**self).save(key, module).await } } @@ -93,32 +93,26 @@ impl CacheError { } } -/// A 256-bit key used for caching. +/// The 256-bit hash of a WebAssembly module. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct Key([u8; 32]); +pub struct ModuleHash([u8; 32]); -impl Key { +impl ModuleHash { pub fn new(key: [u8; 32]) -> Self { - Key(key) + ModuleHash(key) } - /// Generate a new [`Key`] based on the SHA-256 hash of some bytes. - pub fn sha256(data: impl AsRef<[u8]>) -> Self { - let mut hasher = Sha256::default(); - hasher.update(data); - Key::new(hasher.finalize().into()) - } + /// Generate a new [`ModuleCache`] based on the SHA-256 hash of some bytes. + pub fn sha256(wasm: impl AsRef<[u8]>) -> Self { + let wasm = wasm.as_ref(); + debug_assert!( + wasm.starts_with(b"\0asm"), + "It only makes sense to compute a module hash for a WebAssembly module", + ); - /// Generate a new [`Key`] which combines this key with the hash of some - /// extra data. - /// - /// If combining a large amount of data, you probably want to hash with - /// [`Sha256`] directly. - pub fn combined_with(self, other_data: impl AsRef<[u8]>) -> Self { let mut hasher = Sha256::default(); - hasher.update(self.0); - hasher.update(other_data); - Key::new(hasher.finalize().into()) + hasher.update(wasm); + ModuleHash::new(hasher.finalize().into()) } /// Get the raw bytes for. @@ -127,7 +121,7 @@ impl Key { } } -impl Display for Key { +impl Display for ModuleHash { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { for byte in self.0 { write!(f, "{byte:02X}")?; @@ -148,7 +142,7 @@ mod tests { #[test] fn key_is_displayed_as_hex() { - let key = Key::new([ + let key = ModuleHash::new([ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, From e4796bdb8eeff9548583548437c468f36f625cb7 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Mon, 8 May 2023 14:48:59 +0800 Subject: [PATCH 14/18] Add an &Engine parameter to ModuleCache::save() so implementations can cache by deterministic ID --- lib/wasi/src/bin_factory/exec.rs | 2 +- lib/wasi/src/runtime/module_cache/and_then.rs | 13 +++++--- .../src/runtime/module_cache/filesystem.rs | 32 +++++++++++-------- lib/wasi/src/runtime/module_cache/shared.rs | 13 ++++++-- .../src/runtime/module_cache/thread_local.rs | 22 +++++++++---- lib/wasi/src/runtime/module_cache/types.rs | 21 +++++++++--- 6 files changed, 70 insertions(+), 33 deletions(-) diff --git a/lib/wasi/src/bin_factory/exec.rs b/lib/wasi/src/bin_factory/exec.rs index 4c155a71697..a2986cb196e 100644 --- a/lib/wasi/src/bin_factory/exec.rs +++ b/lib/wasi/src/bin_factory/exec.rs @@ -46,7 +46,7 @@ pub async fn spawn_exec( } let module = module?; - if let Err(e) = compiled_modules.save(key, &module).await { + if let Err(e) = compiled_modules.save(key, store.engine(), &module).await { tracing::debug!( %key, package_name=%binary.package_name, diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs index f0713a934a9..aa2606963fb 100644 --- a/lib/wasi/src/runtime/module_cache/and_then.rs +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -54,7 +54,7 @@ where if let Ok(m) = self.secondary.load(key, engine).await { // Now we've got a module, let's make sure it ends up in the primary // cache too. - if let Err(e) = self.primary.save(key, &m).await { + if let Err(e) = self.primary.save(key, engine, &m).await { tracing::warn!( %key, error = &e as &dyn std::error::Error, @@ -68,10 +68,15 @@ where Err(primary_error) } - async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { + async fn save( + &self, + key: ModuleHash, + engine: &Engine, + module: &Module, + ) -> Result<(), CacheError> { futures::try_join!( - self.primary.save(key, module), - self.secondary.save(key, module) + self.primary.save(key, engine, module), + self.secondary.save(key, engine, module) )?; Ok(()) } diff --git a/lib/wasi/src/runtime/module_cache/filesystem.rs b/lib/wasi/src/runtime/module_cache/filesystem.rs index 6af145ac0e2..0d854d34204 100644 --- a/lib/wasi/src/runtime/module_cache/filesystem.rs +++ b/lib/wasi/src/runtime/module_cache/filesystem.rs @@ -26,11 +26,10 @@ impl FileSystemCache { &self.cache_dir } - fn path(&self, key: ModuleHash) -> PathBuf { + fn path(&self, key: ModuleHash, deterministic_id: &str) -> PathBuf { let artifact_version = wasmer_types::MetadataHeader::CURRENT_VERSION; - let dir = format!("artifact-v{artifact_version}"); self.cache_dir - .join(dir) + .join(format!("{deterministic_id}-v{artifact_version}")) .join(key.to_string()) .with_extension("bin") } @@ -39,11 +38,12 @@ impl FileSystemCache { #[async_trait::async_trait] impl ModuleCache for FileSystemCache { async fn load(&self, key: ModuleHash, engine: &Engine) -> Result { - let path = self.path(key); + let path = self.path(key, engine.deterministic_id()); // FIXME: This will all block the thread at the moment. Ideally, // deserializing and uncompressing would happen on a thread pool in the // background. + // https://github.com/wasmerio/wasmer/issues/3851 let uncompressed = read_compressed(&path)?; @@ -71,12 +71,18 @@ impl ModuleCache for FileSystemCache { } } - async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { - let path = self.path(key); + async fn save( + &self, + key: ModuleHash, + engine: &Engine, + module: &Module, + ) -> Result<(), CacheError> { + let path = self.path(key, engine.deterministic_id()); // FIXME: This will all block the thread at the moment. Ideally, // serializing and compressing would happen on a thread pool in the // background. + // https://github.com/wasmerio/wasmer/issues/3851 if let Some(parent) = path.parent() { if let Err(e) = std::fs::create_dir_all(parent) { @@ -107,10 +113,10 @@ impl ModuleCache for FileSystemCache { ); } - return Err(CacheError::Write { path, error: e }); + return Err(CacheError::FileWrite { path, error: e }); } - std::fs::rename(&tmp, &path).map_err(|error| CacheError::Write { path, error }) + std::fs::rename(&tmp, &path).map_err(|error| CacheError::FileWrite { path, error }) } } @@ -133,7 +139,7 @@ fn read_compressed(path: &Path) -> Result, CacheError> { return Err(CacheError::NotFound); } Err(error) => { - return Err(CacheError::Read { + return Err(CacheError::FileRead { path: path.to_path_buf(), error, }); @@ -174,9 +180,9 @@ mod tests { let module = Module::new(&engine, ADD_WAT).unwrap(); let cache = FileSystemCache::new(temp.path()); let key = ModuleHash::new([0; 32]); - let expected_path = cache.path(key); + let expected_path = cache.path(key, engine.deterministic_id()); - cache.save(key, &module).await.unwrap(); + cache.save(key, &engine, &module).await.unwrap(); assert!(expected_path.exists()); } @@ -191,7 +197,7 @@ mod tests { let cache = FileSystemCache::new(&cache_dir); let key = ModuleHash::new([0; 32]); - cache.save(key, &module).await.unwrap(); + cache.save(key, &engine, &module).await.unwrap(); assert!(cache_dir.is_dir()); } @@ -215,7 +221,7 @@ mod tests { let module = Module::new(&engine, ADD_WAT).unwrap(); let key = ModuleHash::new([0; 32]); let cache = FileSystemCache::new(temp.path()); - let expected_path = cache.path(key); + let expected_path = cache.path(key, engine.deterministic_id()); std::fs::create_dir_all(expected_path.parent().unwrap()).unwrap(); let serialized = module.serialize().unwrap(); save_compressed(&expected_path, &serialized).unwrap(); diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index 73bc0a90735..95d06370951 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -6,7 +6,7 @@ use crate::runtime::module_cache::{CacheError, ModuleCache, ModuleHash}; /// A [`ModuleCache`] based on a [DashMap]<[ModuleHash], [Module]>. #[derive(Debug, Default, Clone)] pub struct SharedCache { - modules: DashMap, + modules: DashMap<(ModuleHash, String), Module>, } impl SharedCache { @@ -17,14 +17,21 @@ impl SharedCache { #[async_trait::async_trait] impl ModuleCache for SharedCache { - async fn load(&self, key: ModuleHash, _engine: &Engine) -> Result { + async fn load(&self, key: ModuleHash, engine: &Engine) -> Result { + let key = (key, engine.deterministic_id().to_string()); self.modules .get(&key) .map(|m| m.value().clone()) .ok_or(CacheError::NotFound) } - async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { + async fn save( + &self, + key: ModuleHash, + engine: &Engine, + module: &Module, + ) -> Result<(), CacheError> { + let key = (key, engine.deterministic_id().to_string()); self.modules.insert(key, module.clone()); Ok(()) diff --git a/lib/wasi/src/runtime/module_cache/thread_local.rs b/lib/wasi/src/runtime/module_cache/thread_local.rs index 6937df9e6d6..4b33f7c6258 100644 --- a/lib/wasi/src/runtime/module_cache/thread_local.rs +++ b/lib/wasi/src/runtime/module_cache/thread_local.rs @@ -5,7 +5,7 @@ use wasmer::{Engine, Module}; use crate::runtime::module_cache::{CacheError, ModuleCache, ModuleHash}; std::thread_local! { - static CACHED_MODULES: RefCell> + static CACHED_MODULES: RefCell> = RefCell::new(HashMap::new()); } @@ -15,23 +15,31 @@ std::thread_local! { pub struct ThreadLocalCache {} impl ThreadLocalCache { - fn lookup(&self, key: ModuleHash) -> Option { + fn lookup(&self, key: ModuleHash, deterministic_id: &str) -> Option { + let key = (key, deterministic_id.to_string()); CACHED_MODULES.with(|m| m.borrow().get(&key).cloned()) } - fn insert(&self, key: ModuleHash, module: &Module) { + fn insert(&self, key: ModuleHash, module: &Module, deterministic_id: &str) { + let key = (key, deterministic_id.to_string()); CACHED_MODULES.with(|m| m.borrow_mut().insert(key, module.clone())); } } #[async_trait::async_trait] impl ModuleCache for ThreadLocalCache { - async fn load(&self, key: ModuleHash, _engine: &Engine) -> Result { - self.lookup(key).ok_or(CacheError::NotFound) + async fn load(&self, key: ModuleHash, engine: &Engine) -> Result { + self.lookup(key, engine.deterministic_id()) + .ok_or(CacheError::NotFound) } - async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { - self.insert(key, module); + async fn save( + &self, + key: ModuleHash, + engine: &Engine, + module: &Module, + ) -> Result<(), CacheError> { + self.insert(key, module, engine.deterministic_id()); Ok(()) } } diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index a55a5015902..cb79ca4006c 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -23,7 +23,12 @@ use crate::runtime::module_cache::AndThen; pub trait ModuleCache: Debug { async fn load(&self, key: ModuleHash, engine: &Engine) -> Result; - async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError>; + async fn save( + &self, + key: ModuleHash, + engine: &Engine, + module: &Module, + ) -> Result<(), CacheError>; /// Chain a second cache onto this one. /// @@ -57,8 +62,13 @@ where (**self).load(key, engine).await } - async fn save(&self, key: ModuleHash, module: &Module) -> Result<(), CacheError> { - (**self).save(key, module).await + async fn save( + &self, + key: ModuleHash, + engine: &Engine, + module: &Module, + ) -> Result<(), CacheError> { + (**self).save(key, engine, module).await } } @@ -69,13 +79,13 @@ pub enum CacheError { #[error("Unable to deserialize the module")] Deserialize(#[from] wasmer::DeserializeError), #[error("Unable to read from \"{}\"", path.display())] - Read { + FileRead { path: PathBuf, #[source] error: std::io::Error, }, #[error("Unable to write to \"{}\"", path.display())] - Write { + FileWrite { path: PathBuf, #[source] error: std::io::Error, @@ -83,6 +93,7 @@ pub enum CacheError { /// The item was not found. #[error("Not found")] NotFound, + /// A catch-all variant for any other errors that may occur. #[error(transparent)] Other(Box), } From 658d2e30b5a5bd6ef71eb87066d7b9940caff182 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Mon, 8 May 2023 16:15:48 +0800 Subject: [PATCH 15/18] Adding docs and more tests --- lib/wasi/src/runtime/mod.rs | 8 + lib/wasi/src/runtime/module_cache/and_then.rs | 143 ++++++++++++++++++ .../src/runtime/module_cache/filesystem.rs | 8 +- lib/wasi/src/runtime/module_cache/shared.rs | 32 ++++ .../src/runtime/module_cache/thread_local.rs | 32 ++++ lib/wasi/src/runtime/module_cache/types.rs | 43 +++++- 6 files changed, 256 insertions(+), 10 deletions(-) diff --git a/lib/wasi/src/runtime/mod.rs b/lib/wasi/src/runtime/mod.rs index f2edf2f55c9..527258f885c 100644 --- a/lib/wasi/src/runtime/mod.rs +++ b/lib/wasi/src/runtime/mod.rs @@ -154,6 +154,14 @@ impl PluggableRuntime { self } + pub fn set_module_cache(&mut self, module_cache: M) -> &mut Self + where + M: ModuleCache + Send + Sync + 'static, + { + self.module_cache = Arc::new(module_cache); + self + } + pub fn set_resolver( &mut self, resolver: impl PackageResolver + Send + Sync + 'static, diff --git a/lib/wasi/src/runtime/module_cache/and_then.rs b/lib/wasi/src/runtime/module_cache/and_then.rs index aa2606963fb..e6a9669192e 100644 --- a/lib/wasi/src/runtime/module_cache/and_then.rs +++ b/lib/wasi/src/runtime/module_cache/and_then.rs @@ -81,3 +81,146 @@ where Ok(()) } } + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicUsize, Ordering}; + + use super::*; + use crate::runtime::module_cache::SharedCache; + + const ADD_WAT: &[u8] = br#"( + module + (func + (export "add") + (param $x i64) + (param $y i64) + (result i64) + (i64.add (local.get $x) (local.get $y))) + )"#; + + #[derive(Debug)] + struct Spy { + inner: I, + success: AtomicUsize, + failures: AtomicUsize, + } + + impl Spy { + fn new(inner: I) -> Self { + Spy { + inner, + success: AtomicUsize::new(0), + failures: AtomicUsize::new(0), + } + } + + fn success(&self) -> usize { + self.success.load(Ordering::SeqCst) + } + + fn failures(&self) -> usize { + self.failures.load(Ordering::SeqCst) + } + } + + #[async_trait::async_trait] + impl ModuleCache for Spy { + async fn load(&self, key: ModuleHash, engine: &Engine) -> Result { + match self.inner.load(key, engine).await { + Ok(m) => { + self.success.fetch_add(1, Ordering::SeqCst); + Ok(m) + } + Err(e) => { + self.failures.fetch_add(1, Ordering::SeqCst); + Err(e) + } + } + } + + async fn save( + &self, + key: ModuleHash, + engine: &Engine, + module: &Module, + ) -> Result<(), CacheError> { + match self.inner.save(key, engine, module).await { + Ok(_) => { + self.success.fetch_add(1, Ordering::SeqCst); + Ok(()) + } + Err(e) => { + self.failures.fetch_add(1, Ordering::SeqCst); + Err(e) + } + } + } + } + + #[tokio::test] + async fn load_from_primary() { + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let key = ModuleHash::from_raw([0; 32]); + let primary = SharedCache::default(); + let secondary = SharedCache::default(); + primary.save(key, &engine, &module).await.unwrap(); + let primary = Spy::new(primary); + let secondary = Spy::new(secondary); + let cache = AndThen::new(&primary, &secondary); + + let got = cache.load(key, &engine).await.unwrap(); + + // We should have received the same module + assert_eq!(module, got); + assert_eq!(primary.success(), 1); + assert_eq!(primary.failures(), 0); + // but the secondary wasn't touched at all + assert_eq!(secondary.success(), 0); + assert_eq!(secondary.failures(), 0); + // And the secondary still doesn't have our module + assert!(secondary.load(key, &engine).await.is_err()); + } + + #[tokio::test] + async fn loading_from_secondary_also_populates_primary() { + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let key = ModuleHash::from_raw([0; 32]); + let primary = SharedCache::default(); + let secondary = SharedCache::default(); + secondary.save(key, &engine, &module).await.unwrap(); + let primary = Spy::new(primary); + let secondary = Spy::new(secondary); + let cache = AndThen::new(&primary, &secondary); + + let got = cache.load(key, &engine).await.unwrap(); + + // We should have received the same module + assert_eq!(module, got); + // We got a hit on the secondary + assert_eq!(secondary.success(), 1); + assert_eq!(secondary.failures(), 0); + // the load() on our primary failed + assert_eq!(primary.failures(), 1); + // but afterwards, we updated the primary cache with our module + assert_eq!(primary.success(), 1); + assert_eq!(primary.load(key, &engine).await.unwrap(), module); + } + + #[tokio::test] + async fn saving_will_update_both() { + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let key = ModuleHash::from_raw([0; 32]); + let primary = SharedCache::default(); + let secondary = SharedCache::default(); + let cache = AndThen::new(&primary, &secondary); + + cache.save(key, &engine, &module).await.unwrap(); + + assert_eq!(primary.load(key, &engine).await.unwrap(), module); + assert_eq!(secondary.load(key, &engine).await.unwrap(), module); + } +} diff --git a/lib/wasi/src/runtime/module_cache/filesystem.rs b/lib/wasi/src/runtime/module_cache/filesystem.rs index 0d854d34204..bfd58e80d1b 100644 --- a/lib/wasi/src/runtime/module_cache/filesystem.rs +++ b/lib/wasi/src/runtime/module_cache/filesystem.rs @@ -179,7 +179,7 @@ mod tests { let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); let cache = FileSystemCache::new(temp.path()); - let key = ModuleHash::new([0; 32]); + let key = ModuleHash::from_raw([0; 32]); let expected_path = cache.path(key, engine.deterministic_id()); cache.save(key, &engine, &module).await.unwrap(); @@ -195,7 +195,7 @@ mod tests { let cache_dir = temp.path().join("this").join("doesn't").join("exist"); assert!(!cache_dir.exists()); let cache = FileSystemCache::new(&cache_dir); - let key = ModuleHash::new([0; 32]); + let key = ModuleHash::from_raw([0; 32]); cache.save(key, &engine, &module).await.unwrap(); @@ -206,7 +206,7 @@ mod tests { async fn missing_file() { let temp = TempDir::new().unwrap(); let engine = Engine::default(); - let key = ModuleHash::new([0; 32]); + let key = ModuleHash::from_raw([0; 32]); let cache = FileSystemCache::new(temp.path()); let err = cache.load(key, &engine).await.unwrap_err(); @@ -219,7 +219,7 @@ mod tests { let temp = TempDir::new().unwrap(); let engine = Engine::default(); let module = Module::new(&engine, ADD_WAT).unwrap(); - let key = ModuleHash::new([0; 32]); + let key = ModuleHash::from_raw([0; 32]); let cache = FileSystemCache::new(temp.path()); let expected_path = cache.path(key, engine.deterministic_id()); std::fs::create_dir_all(expected_path.parent().unwrap()).unwrap(); diff --git a/lib/wasi/src/runtime/module_cache/shared.rs b/lib/wasi/src/runtime/module_cache/shared.rs index 95d06370951..05476e26a55 100644 --- a/lib/wasi/src/runtime/module_cache/shared.rs +++ b/lib/wasi/src/runtime/module_cache/shared.rs @@ -37,3 +37,35 @@ impl ModuleCache for SharedCache { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + const ADD_WAT: &[u8] = br#"( + module + (func + (export "add") + (param $x i64) + (param $y i64) + (result i64) + (i64.add (local.get $x) (local.get $y))) + )"#; + + #[tokio::test] + async fn round_trip_via_cache() { + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let cache = SharedCache::default(); + let key = ModuleHash::from_raw([0; 32]); + + cache.save(key, &engine, &module).await.unwrap(); + let round_tripped = cache.load(key, &engine).await.unwrap(); + + let exports: Vec<_> = round_tripped + .exports() + .map(|export| export.name().to_string()) + .collect(); + assert_eq!(exports, ["add"]); + } +} diff --git a/lib/wasi/src/runtime/module_cache/thread_local.rs b/lib/wasi/src/runtime/module_cache/thread_local.rs index 4b33f7c6258..f7212c11f8f 100644 --- a/lib/wasi/src/runtime/module_cache/thread_local.rs +++ b/lib/wasi/src/runtime/module_cache/thread_local.rs @@ -43,3 +43,35 @@ impl ModuleCache for ThreadLocalCache { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + const ADD_WAT: &[u8] = br#"( + module + (func + (export "add") + (param $x i64) + (param $y i64) + (result i64) + (i64.add (local.get $x) (local.get $y))) + )"#; + + #[tokio::test(flavor = "current_thread")] + async fn round_trip_via_cache() { + let engine = Engine::default(); + let module = Module::new(&engine, ADD_WAT).unwrap(); + let cache = ThreadLocalCache::default(); + let key = ModuleHash::from_raw([0; 32]); + + cache.save(key, &engine, &module).await.unwrap(); + let round_tripped = cache.load(key, &engine).await.unwrap(); + + let exports: Vec<_> = round_tripped + .exports() + .map(|export| export.name().to_string()) + .collect(); + assert_eq!(exports, ["add"]); + } +} diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index cb79ca4006c..ad56929d0dd 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -11,6 +11,11 @@ use crate::runtime::module_cache::AndThen; /// A cache for compiled WebAssembly modules. /// +/// ## Deterministic ID +/// +/// Implementations are encouraged to take the [`Engine::deterministic_id()`] +/// into account when saving and loading cached a [`Module`]. +/// /// ## Assumptions /// /// Implementations can assume that cache keys are unique and that using the @@ -19,10 +24,20 @@ use crate::runtime::module_cache::AndThen; /// Implementations can also assume that [`ModuleCache::load()`] will /// be called more often than [`ModuleCache::save()`] and optimise /// their caching strategy accordingly. +/// #[async_trait::async_trait] pub trait ModuleCache: Debug { + /// Load a module based on its hash. async fn load(&self, key: ModuleHash, engine: &Engine) -> Result; + /// Save a module so it can be retrieved with [`ModuleCache::load()`] at a + /// later time. + /// + /// # Panics + /// + /// Implementations are free to assume the [`Module`] being passed in was + /// compiled using the provided [`Engine`], and may panic if this isn't the + /// case. async fn save( &self, key: ModuleHash, @@ -72,6 +87,7 @@ where } } +/// Possible errors that may occur during [`ModuleCache`] operations. #[derive(Debug, thiserror::Error)] pub enum CacheError { #[error("Unable to serialize the module")] @@ -104,12 +120,13 @@ impl CacheError { } } -/// The 256-bit hash of a WebAssembly module. +/// The SHA-256 hash of a WebAssembly module. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ModuleHash([u8; 32]); impl ModuleHash { - pub fn new(key: [u8; 32]) -> Self { + /// Create a new [`ModuleHash`] from the raw SHA-256 hash. + pub fn from_raw(key: [u8; 32]) -> Self { ModuleHash(key) } @@ -123,11 +140,11 @@ impl ModuleHash { let mut hasher = Sha256::default(); hasher.update(wasm); - ModuleHash::new(hasher.finalize().into()) + ModuleHash::from_raw(hasher.finalize().into()) } - /// Get the raw bytes for. - pub fn as_bytes(self) -> [u8; 32] { + /// Get the raw SHA-256 hash. + pub fn as_raw(self) -> [u8; 32] { self.0 } } @@ -153,7 +170,7 @@ mod tests { #[test] fn key_is_displayed_as_hex() { - let key = ModuleHash::new([ + let key = ModuleHash::from_raw([ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, @@ -166,4 +183,18 @@ mod tests { "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F" ); } + + #[test] + fn module_hash_is_just_sha_256() { + let wasm = b"\0asm..."; + let raw = [ + 0x5a, 0x39, 0xfe, 0xef, 0x52, 0xe5, 0x3b, 0x8f, 0xfe, 0xdf, 0xd7, 0x05, 0x15, 0x56, + 0xec, 0x10, 0x5e, 0xd8, 0x69, 0x82, 0xf1, 0x22, 0xa0, 0x5d, 0x27, 0x28, 0xd9, 0x67, + 0x78, 0xe4, 0xeb, 0x96, + ]; + + let hash = ModuleHash::sha256(wasm); + + assert_eq!(hash.as_raw(), raw); + } } From efc811fd55d05f86ad25420c2bfe099752370867 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Mon, 8 May 2023 16:29:07 +0800 Subject: [PATCH 16/18] Use the tempfile crate for the filesystem cache's temporary file --- lib/wasi/Cargo.toml | 6 +- .../src/runtime/module_cache/filesystem.rs | 60 ++++++++----------- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index bcd336ce725..1142bffc2b5 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -53,6 +53,9 @@ wai-bindgen-wasmer = { path = "../wai-bindgen-wasmer", version = "0.4.0", featur heapless = "0.7.16" once_cell = "1.17.0" pin-project = "1.0.12" +semver = "1.0.17" +dashmap = "5.4.0" +tempfile = "3.4.0" # Used by the WCGI runner hyper = { version = "0.14", features = ["server", "stream"], optional = true } wcgi = { version = "0.1.2", optional = true } @@ -60,8 +63,6 @@ wcgi-host = { version = "0.1.2", optional = true } tower-http = { version = "0.4.0", features = ["trace", "util", "catch-panic", "cors"], optional = true } tower = { version = "0.4.13", features = ["make", "util"], optional = true } url = "2.3.1" -semver = "1.0.17" -dashmap = "5.4.0" [target.'cfg(not(target_arch = "riscv64"))'.dependencies.reqwest] version = "0.11" @@ -90,7 +91,6 @@ wasm-bindgen = "0.2.74" [dev-dependencies] wasmer = { path = "../api", version = "=3.3.0", default-features = false, features = ["wat", "js-serializable-module"] } tokio = { version = "1", features = [ "sync", "macros", "rt" ], default_features = false } -tempfile = "3.4.0" [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3.0" diff --git a/lib/wasi/src/runtime/module_cache/filesystem.rs b/lib/wasi/src/runtime/module_cache/filesystem.rs index bfd58e80d1b..81ae8f823f0 100644 --- a/lib/wasi/src/runtime/module_cache/filesystem.rs +++ b/lib/wasi/src/runtime/module_cache/filesystem.rs @@ -1,9 +1,6 @@ -use std::{ - fs::File, - path::{Path, PathBuf}, - sync::atomic::{AtomicU64, Ordering}, -}; +use std::path::{Path, PathBuf}; +use tempfile::NamedTempFile; use wasmer::{Engine, Module}; use crate::runtime::module_cache::{CacheError, ModuleCache, ModuleHash}; @@ -84,50 +81,39 @@ impl ModuleCache for FileSystemCache { // background. // https://github.com/wasmerio/wasmer/issues/3851 - if let Some(parent) = path.parent() { - if let Err(e) = std::fs::create_dir_all(parent) { - tracing::warn!( - dir=%parent.display(), - error=&e as &dyn std::error::Error, - "Unable to create the cache directory", - ); - } - } - - // Note: We'll first save to a temporary file in the same folder, then - // rename it when we are done serializing. Try to use a unique extension - // just in case we have concurrent saves of the same module. - static UNIQUE_ID: AtomicU64 = AtomicU64::new(0); - let extension = format!("tmp{}", UNIQUE_ID.fetch_add(1, Ordering::Relaxed)); + let parent = path + .parent() + .expect("Unreachable - always created by joining onto cache_dir"); - let tmp = path.with_extension(extension); + if let Err(e) = std::fs::create_dir_all(parent) { + tracing::warn!( + dir=%parent.display(), + error=&e as &dyn std::error::Error, + "Unable to create the cache directory", + ); + } + // Note: We save to a temporary file and persist() it at the end so + // concurrent readers won't see a partially written module. + let mut f = NamedTempFile::new_in(parent).map_err(CacheError::other)?; let serialized = module.serialize()?; - if let Err(e) = save_compressed(&tmp, &serialized) { - if let Err(e) = std::fs::remove_file(&tmp) { - tracing::warn!( - path=%path.display(), - key=%key, - error=&e as &dyn std::error::Error, - "Unable to remove the temporary file", - ); - } + if let Err(e) = save_compressed(&mut f, &serialized) { return Err(CacheError::FileWrite { path, error: e }); } - std::fs::rename(&tmp, &path).map_err(|error| CacheError::FileWrite { path, error }) + f.persist(&path).map_err(CacheError::other)?; + + Ok(()) } } -fn save_compressed(path: &Path, data: &[u8]) -> Result<(), std::io::Error> { - let mut f = File::create(path)?; +fn save_compressed(writer: impl std::io::Write, data: &[u8]) -> Result<(), std::io::Error> { let mut encoder = weezl::encode::Encoder::new(weezl::BitOrder::Msb, 8); encoder - .into_stream(&mut f) + .into_stream(writer) .encode_all(std::io::Cursor::new(data)) .status?; - f.sync_all()?; Ok(()) } @@ -159,6 +145,8 @@ fn read_compressed(path: &Path) -> Result, CacheError> { #[cfg(test)] mod tests { + use std::fs::File; + use tempfile::TempDir; use super::*; @@ -224,7 +212,7 @@ mod tests { let expected_path = cache.path(key, engine.deterministic_id()); std::fs::create_dir_all(expected_path.parent().unwrap()).unwrap(); let serialized = module.serialize().unwrap(); - save_compressed(&expected_path, &serialized).unwrap(); + save_compressed(File::create(&expected_path).unwrap(), &serialized).unwrap(); let module = cache.load(key, &engine).await.unwrap(); From 0a7a90938a5ed14fbb5c0c94a7ce327f0fb4dd16 Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Mon, 8 May 2023 16:33:55 +0800 Subject: [PATCH 17/18] Removed the debug_assert!() from ModuleHash::sha256() --- lib/wasi/src/runtime/module_cache/types.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/wasi/src/runtime/module_cache/types.rs b/lib/wasi/src/runtime/module_cache/types.rs index ad56929d0dd..8ed0355ec4a 100644 --- a/lib/wasi/src/runtime/module_cache/types.rs +++ b/lib/wasi/src/runtime/module_cache/types.rs @@ -133,10 +133,6 @@ impl ModuleHash { /// Generate a new [`ModuleCache`] based on the SHA-256 hash of some bytes. pub fn sha256(wasm: impl AsRef<[u8]>) -> Self { let wasm = wasm.as_ref(); - debug_assert!( - wasm.starts_with(b"\0asm"), - "It only makes sense to compute a module hash for a WebAssembly module", - ); let mut hasher = Sha256::default(); hasher.update(wasm); From 5bc423702ab971f3e071e39b966697aa1417a91f Mon Sep 17 00:00:00 2001 From: Michael-F-Bryan Date: Mon, 8 May 2023 16:34:14 +0800 Subject: [PATCH 18/18] Make sure "wasmer run" uses the on-disk cache --- lib/cli/src/commands/run/wasi.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/cli/src/commands/run/wasi.rs b/lib/cli/src/commands/run/wasi.rs index 6a44a0a81ea..786a02978ed 100644 --- a/lib/cli/src/commands/run/wasi.rs +++ b/lib/cli/src/commands/run/wasi.rs @@ -14,6 +14,7 @@ use wasmer_wasix::{ os::{tty_sys::SysTty, TtyBridge}, runners::MappedDirectory, runtime::{ + module_cache::{FileSystemCache, ModuleCache}, resolver::{PackageResolver, RegistryResolver}, task_manager::tokio::TokioTaskManager, }, @@ -220,12 +221,17 @@ impl Wasi { rt.set_tty(tty); } - rt.set_engine(Some(engine)); + let wasmer_home = WasmerConfig::get_wasmer_dir().map_err(anyhow::Error::msg)?; let resolver = self - .prepare_resolver() + .prepare_resolver(&wasmer_home) .context("Unable to prepare the package resolver")?; - rt.set_resolver(resolver); + let module_cache = wasmer_wasix::runtime::module_cache::in_memory() + .and_then(FileSystemCache::new(wasmer_home.join("compiled"))); + + rt.set_resolver(resolver) + .set_module_cache(module_cache) + .set_engine(Some(engine)); Ok(rt) } @@ -273,8 +279,8 @@ impl Wasi { }) } - fn prepare_resolver(&self) -> Result { - let mut resolver = wapm_resolver()?; + fn prepare_resolver(&self, wasmer_home: &Path) -> Result { + let mut resolver = wapm_resolver(wasmer_home)?; for path in &self.include_webcs { let pkg = preload_webc(path) @@ -286,14 +292,13 @@ impl Wasi { } } -fn wapm_resolver() -> Result { +fn wapm_resolver(wasmer_home: &Path) -> Result { // FIXME(Michael-F-Bryan): Ideally, all of this would in the // RegistryResolver::from_env() constructor, but we don't want to add // wasmer-registry as a dependency of wasmer-wasix just yet. - let wasmer_home = WasmerConfig::get_wasmer_dir().map_err(anyhow::Error::msg)?; - let cache_dir = wasmer_registry::get_webc_dir(&wasmer_home); + let cache_dir = wasmer_registry::get_webc_dir(wasmer_home); let config = - wasmer_registry::WasmerConfig::from_file(&wasmer_home).map_err(anyhow::Error::msg)?; + wasmer_registry::WasmerConfig::from_file(wasmer_home).map_err(anyhow::Error::msg)?; let registry = config.registry.get_graphql_url(); let registry = registry