core: sausage-vfs per process (/, /proc, /self, /run), activation, graph, watcher, upgrades
- /proc is a core layer: core/ (PROTOCOL.md, graph.json, graph.dot, locks.json, plugins/<name>/discover.json and client.rs) and one folder per process (info.json, log, and its /self files). - Locks are released only after the task ended, the store dropped, and in-flight file operations returned. - core.activate and core.revert with copies, discovery, compatibility checks of imports and consumers, groups, force. An admission gate makes the snapshot of old processes complete. - Graph of discover documents by hash, refreshed by a file watcher. - Upgrades: grace periods (x-sausage-grace), stop, service restarts, rpc.retired. - SDK plugins in the real core (cli/tests/sdk.rs).
This commit is contained in:
+31
-16
@@ -3,7 +3,7 @@
|
||||
//! An instance is one run of a `wasi:cli/command` component. The host writes
|
||||
//! NDJSON lines to its stdin and reads NDJSON lines from its stdout.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -17,7 +17,7 @@ use wasmtime_wasi::cli::{AsyncStdinStream, AsyncStdoutStream};
|
||||
|
||||
use wasmtime_wasi::p2::bindings::Command;
|
||||
|
||||
use wasmtime_wasi::{FsPerms, I32Exit, WasiCtx, WasiCtxView, WasiView};
|
||||
use wasmtime_wasi::{I32Exit, WasiCtx, WasiCtxView, WasiView};
|
||||
use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpCtxView, WasiHttpHooks, WasiHttpView};
|
||||
|
||||
/// The HTTP transport hooks supplied to WASI processes.
|
||||
@@ -61,6 +61,7 @@ pub struct ProcessState {
|
||||
table: ResourceTable,
|
||||
http: WasiHttpCtx,
|
||||
hooks: HttpHooks,
|
||||
fs: sausage_vfs::adapter::Adapter,
|
||||
}
|
||||
|
||||
impl WasiView for ProcessState {
|
||||
@@ -111,8 +112,10 @@ impl std::fmt::Display for Exit {
|
||||
|
||||
/// The filesystem, network, and environment supplied to one process.
|
||||
pub struct InstanceConfig {
|
||||
/// The world folder to preopen as the guest root.
|
||||
pub world: PathBuf,
|
||||
/// The virtual namespace exposed through the single root preopen.
|
||||
pub filesystem: sausage_vfs::Mounts,
|
||||
/// The store completion barrier used by process cleanup.
|
||||
pub store_ended: tokio::sync::watch::Sender<bool>,
|
||||
/// Outgoing HTTP, and TCP, UDP, and DNS through `wasi:sockets`.
|
||||
pub network: bool,
|
||||
/// The environment variables supplied to the guest.
|
||||
@@ -127,12 +130,20 @@ pub struct Instance {
|
||||
pub stdout: mpsc::Receiver<String>,
|
||||
/// The stderr lines that the process writes.
|
||||
pub stderr: mpsc::Receiver<String>,
|
||||
task: JoinHandle<Exit>,
|
||||
task: Option<JoinHandle<Exit>>,
|
||||
store_ended: tokio::sync::watch::Sender<bool>,
|
||||
}
|
||||
|
||||
impl Drop for Instance {
|
||||
fn drop(&mut self) {
|
||||
self.task.abort();
|
||||
if let Some(task) = self.task.take() {
|
||||
task.abort();
|
||||
let ended = self.store_ended.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = task.await;
|
||||
ended.send_replace(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +177,10 @@ impl Instance {
|
||||
|
||||
/// Wait until the guest ends.
|
||||
pub async fn wait(&mut self) -> Exit {
|
||||
match (&mut self.task).await {
|
||||
let result = self.task.as_mut().unwrap().await;
|
||||
self.task.take();
|
||||
self.store_ended.send_replace(true);
|
||||
match result {
|
||||
Ok(exit) => exit,
|
||||
Err(e) if e.is_cancelled() => Exit::Trap("killed".into()),
|
||||
Err(e) => Exit::Trap(format!("host task failed: {e}")),
|
||||
@@ -198,6 +212,10 @@ impl Runtime {
|
||||
|
||||
let mut linker: Linker<ProcessState> = Linker::new(&engine);
|
||||
wasmtime_wasi::p2::add_to_linker_async(&mut linker)?;
|
||||
sausage_vfs::adapter::add_to_linker(&mut linker, |state| sausage_vfs::adapter::View {
|
||||
adapter: &mut state.fs,
|
||||
table: &mut state.table,
|
||||
})?;
|
||||
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)?;
|
||||
// Time slices: every 10 ms each running guest yields to the scheduler.
|
||||
let runtime = Arc::new(Self { engine, linker });
|
||||
@@ -220,7 +238,7 @@ impl Runtime {
|
||||
Ok(Component::new(&self.engine, bytes)?)
|
||||
}
|
||||
|
||||
/// Prepare a world preopen off the async workers, then start the WASI task.
|
||||
/// Start a WASI task with the supplied virtual namespace.
|
||||
pub async fn start(
|
||||
self: &Arc<Self>,
|
||||
component: &Component,
|
||||
@@ -243,18 +261,13 @@ impl Runtime {
|
||||
for (k, v) in &cfg.env {
|
||||
b.env(k, v);
|
||||
}
|
||||
// Filesystem setup lives here. T6 replaces this preopen with the VFS.
|
||||
let world = cfg.world;
|
||||
let mut b = tokio::task::spawn_blocking(move || -> Result<_> {
|
||||
b.preopened_dir(world, "/", FsPerms::ReadWrite)?;
|
||||
Ok(b)
|
||||
})
|
||||
.await??;
|
||||
|
||||
let state = ProcessState {
|
||||
ctx: b.build(),
|
||||
table: ResourceTable::new(),
|
||||
http: WasiHttpCtx::new(),
|
||||
hooks: HttpHooks { allow: cfg.network },
|
||||
fs: sausage_vfs::adapter::Adapter::new(cfg.filesystem),
|
||||
};
|
||||
|
||||
let (line_tx, line_rx) = mpsc::channel(1024);
|
||||
@@ -285,6 +298,7 @@ impl Runtime {
|
||||
});
|
||||
let rt = self.clone();
|
||||
let component = component.clone();
|
||||
cfg.store_ended.send_replace(false);
|
||||
let task = tokio::spawn(async move {
|
||||
let mut store = Store::new(&rt.engine, state);
|
||||
store.epoch_deadline_async_yield_and_update(1);
|
||||
@@ -311,7 +325,8 @@ impl Runtime {
|
||||
stdin: Some(host_in),
|
||||
stdout: line_rx,
|
||||
stderr: err_rx,
|
||||
task,
|
||||
task: Some(task),
|
||||
store_ended: cfg.store_ended,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user