Merge v3 with T15 into v3-t22

This commit is contained in:
2026-11-06 22:20:05 +00:00
171 changed files with 33754 additions and 481 deletions
+60 -47
View File
@@ -5,7 +5,7 @@ use crate::{
};
use sausage_vfs::{
Mounts,
adapter::{self, Adapter, View},
adapter::{self, Adapter},
};
use std::{
collections::HashMap,
@@ -22,7 +22,7 @@ use tokio::{
};
use wasmtime::{
Engine, Store,
component::{Component, Linker, Resource, ResourceTable},
component::{Resource, ResourceTable},
};
use wasmtime_wasi::{
WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView,
@@ -46,6 +46,8 @@ mod bindings {
});
}
use bindings::sausage::process::host::{Host, HostChild, Request, Stdio};
mod cache;
pub use cache::configure as configure_cache;
mod services;
mod watcher;
pub use watcher::Watcher;
@@ -80,7 +82,8 @@ struct Entry {
pub struct Runtime {
world: World,
engine: Engine,
cache: Mutex<HashMap<Vec<u8>, Compiled>>,
compiler: Arc<cache::Compiler>,
cache: Mutex<std::collections::HashSet<Vec<u8>>>,
entries: Mutex<HashMap<u32, Entry>>,
next: AtomicU32,
alive: Arc<AtomicBool>,
@@ -141,14 +144,14 @@ impl Runtime {
Self::new(World::open(path)?)
}
pub fn new(world: World) -> anyhow::Result<Arc<Self>> {
// Deno and WASI HTTP enable different providers. Select one before HTTPS creates a client.
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
let mut config = wasmtime::Config::new();
config.epoch_interruption(true);
let mut cache = wasmtime::CacheConfig::new();
cache.with_directory(world.host_dir.join("compile-cache"));
config.cache(Some(wasmtime::Cache::new(cache)?));
let engine = Engine::new(&config)?;
Self::with_compiler(world, cache::shared()?)
}
/// Open an independent engine using an explicit shared disk cache.
pub fn new_with_cache(world: World, directory: &Path) -> anyhow::Result<Arc<Self>> {
Self::with_compiler(world, cache::Compiler::new(directory)?)
}
fn with_compiler(world: World, compiler: Arc<cache::Compiler>) -> anyhow::Result<Arc<Self>> {
let engine = compiler.engine.clone();
let alive = Arc::new(AtomicBool::new(true));
let ticking = alive.clone();
let clock = engine.clone();
@@ -158,19 +161,57 @@ impl Runtime {
clock.increment_epoch();
}
});
Ok(Arc::new(Self {
let runtime = Arc::new(Self {
world,
engine,
alive,
cache: Mutex::new(HashMap::new()),
compiler,
cache: Mutex::new(Default::default()),
entries: Mutex::new(HashMap::new()),
next: AtomicU32::new(1),
}))
});
cache::precompile(runtime.compiler.clone(), runtime.world.root.clone());
Ok(runtime)
}
/// Number of distinct program contents compiled by this runtime.
pub fn compiled_programs(&self) -> usize {
self.cache.lock().unwrap().len()
}
async fn resolve_program(
&self,
cwd: &str,
name: &str,
env: &[(String, String)],
) -> anyhow::Result<String> {
let paths = if name.contains('/') {
vec![sausage_vfs::resolve(cwd, name)]
} else {
let path = env
.iter()
.find(|(key, _)| key == "PATH")
.map(|(_, value)| value.as_str())
.unwrap_or("/usr/local/bin:/bin");
path.split(':')
.map(|folder| {
sausage_vfs::resolve(
cwd,
&std::path::Path::new(folder).join(name).to_string_lossy(),
)
})
.collect()
};
for path in paths {
for candidate in [path.clone(), format!("{path}.wasm")] {
match self.world.root.stat(&candidate, true).await {
Ok(stat) if !stat.directory => return Ok(candidate),
Ok(_) => {}
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
Err(e) => return Err(e.into()),
}
}
}
anyhow::bail!("program not found: {name}")
}
fn kill_command(&self, command: u32) {
for entry in self.entries.lock().unwrap().values() {
if entry.command == command {
@@ -225,7 +266,9 @@ impl Runtime {
request.cwd = sausage_vfs::resolve("/", &request.cwd);
request.env.retain(|(name, _)| name != "PWD");
request.env.push(("PWD".into(), request.cwd.clone()));
let executable = sausage_vfs::resolve(&request.cwd, &request.program);
let executable = self
.resolve_program(&request.cwd, &request.program, &request.env)
.await?;
let file = self
.world
.root
@@ -240,38 +283,8 @@ impl Runtime {
let bytes = file
.read(0, usize::try_from(file.stat().await?.size)?)
.await?;
let pre = {
let mut cache = self.cache.lock().unwrap();
if let Some(pre) = cache.get(&bytes) {
pre.clone()
} else {
let pre = if bytes.get(..8) == Some(b"\0asm\x01\0\0\0") {
let module = wasmtime::Module::new(&self.engine, &bytes)?;
let mut linker = wasmtime::Linker::new(&self.engine);
sausage_vfs::preview1::add_to_linker(&mut linker, |s: &mut State| {
s.preview1.as_mut().unwrap()
})?;
Compiled::Module(linker.instantiate_pre(&module)?)
} else {
let component = Component::new(&self.engine, &bytes)?;
let mut linker = Linker::new(&self.engine);
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
// The library linker includes exit-with-code. Only Wasmtime's CLI gates it.
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
adapter::add_to_linker(&mut linker, |s: &mut State| View {
adapter: &mut s.fs,
table: &mut s.table,
})?;
bindings::sausage::process::host::add_to_linker::<
_,
wasmtime::component::HasSelf<_>,
>(&mut linker, |s| s)?;
Compiled::Component(CommandPre::new(linker.instantiate_pre(&component)?)?)
};
cache.insert(bytes, pre.clone());
pre
}
};
let pre = self.compiler.get(bytes.clone()).await?;
self.cache.lock().unwrap().insert(bytes);
let pid = self.next.fetch_add(1, Ordering::Relaxed);
let command = if command == 0 { pid } else { command };
let group = match request.group {