- The cargo target folder is <cache>/cargo-target, next to Wasmtime's cache, which moves to <cache>/wasmtime: Wasmtime's cleanup worker deletes unknown entries in its folder, also Cargo's artifacts. - Builds use the SDK at <cache>/sdk/<content hash>/, so cargo reuses the compiled SDK in every world. /plugins/cargo/sdk/ stays for agents. - Tests run in the release profile and share compiled crates with the release build. - The cargo and cli tests use a persistent cache in CARGO_TARGET_TMPDIR: warm, the cargo suite takes 11 s (was 235 s) and the cli suite 6 s (was 149 s). - nix flake check runs ignored stress tests. AGENTS.md: rules for fast tests.
156 lines
5.9 KiB
Rust
156 lines
5.9 KiB
Rust
//! Host startup, boot calls, and the public library API.
|
|
use crate::{RpcResult, components, journal, runtime::Runtime, world::World};
|
|
use crate::{
|
|
locks::Lock,
|
|
native::NativePlugin,
|
|
process::{Process, ProcessInfo},
|
|
};
|
|
use serde_json::{Value, json};
|
|
use std::{
|
|
collections::HashMap,
|
|
path::Path,
|
|
sync::{Arc, Mutex},
|
|
};
|
|
use tokio::sync::watch;
|
|
use uuid::Uuid;
|
|
pub(crate) struct State {
|
|
pub(crate) processes: HashMap<String, Process>,
|
|
pub(crate) locks: HashMap<String, Lock>,
|
|
pub(crate) native: HashMap<String, NativePlugin>,
|
|
pub(crate) services: Vec<crate::services::Service>,
|
|
}
|
|
pub(crate) struct Inner {
|
|
pub(crate) world: World,
|
|
pub(crate) runtime: Arc<Runtime>,
|
|
pub(crate) state: Mutex<State>,
|
|
pub(crate) journal: Arc<journal::Journal>,
|
|
pub(crate) notifications: tokio::sync::broadcast::Sender<Value>,
|
|
pub(crate) exit: watch::Sender<bool>,
|
|
pub(crate) components: components::Components,
|
|
/// Calls hold it for reading from the choice of a version until their
|
|
/// process is in the table. Activation holds it for writing while it
|
|
/// installs a version, so that it sees every process of the old version.
|
|
pub(crate) admission: tokio::sync::RwLock<()>,
|
|
/// Set when the host starts to stop. After that, the core starts no new
|
|
/// processes, and upgrade tasks do nothing.
|
|
pub(crate) shutdown: std::sync::atomic::AtomicBool,
|
|
pub(crate) run: sausage_vfs::Memory,
|
|
pub(crate) graph: Mutex<crate::graph::Graph>,
|
|
pub(crate) activations: tokio::sync::Mutex<()>,
|
|
pub(crate) watcher: Mutex<Option<notify::RecommendedWatcher>>,
|
|
pub(crate) retirements: Mutex<std::collections::HashSet<(String, String)>>,
|
|
}
|
|
#[derive(Clone)]
|
|
/// A cloneable handle to a world and its running processes.
|
|
pub struct Host(pub(crate) Arc<Inner>);
|
|
|
|
impl Host {
|
|
/// Start a world with the default compile cache and its ordered boot calls.
|
|
pub async fn start(path: &Path) -> anyhow::Result<Self> {
|
|
let base = std::env::var_os("XDG_CACHE_HOME")
|
|
.map(std::path::PathBuf::from)
|
|
.unwrap_or_else(|| {
|
|
std::path::PathBuf::from(std::env::var_os("HOME").unwrap_or_default())
|
|
.join(".cache")
|
|
});
|
|
Self::start_with_cache(path, &base.join("sausage")).await
|
|
}
|
|
/// Start a world with an explicit compile cache folder.
|
|
pub async fn start_with_cache(path: &Path, cache: &Path) -> anyhow::Result<Self> {
|
|
let path = path.to_path_buf();
|
|
let cargo_cache = cache.to_path_buf();
|
|
let cache = cache.to_path_buf();
|
|
let (world, runtime) = tokio::task::spawn_blocking(move || -> anyhow::Result<_> {
|
|
Ok((World::open(&path)?, Runtime::new(&cache)?))
|
|
})
|
|
.await??;
|
|
let journal =
|
|
journal::Journal::open(world.state_dir("core").join("journal.sqlite")).await?;
|
|
let host = Self::with_parts(world, runtime, journal);
|
|
let cargo_world = host.0.world.clone();
|
|
let cargo = tokio::task::spawn_blocking(move || {
|
|
crate::cargo::Cargo::new(cargo_world, &cargo_cache)
|
|
})
|
|
.await??;
|
|
cargo.register(&host);
|
|
host.boot().await?;
|
|
Ok(host)
|
|
}
|
|
/// Creates an empty test world with a shared engine and an in-memory journal.
|
|
pub async fn test_world(path: &Path, runtime: Arc<Runtime>) -> anyhow::Result<Self> {
|
|
std::fs::create_dir_all(path)?;
|
|
Ok(Self::with_parts(
|
|
World::open(path)?,
|
|
runtime,
|
|
journal::Journal::memory().await?,
|
|
))
|
|
}
|
|
fn with_parts(world: World, runtime: Arc<Runtime>, journal: journal::Journal) -> Self {
|
|
Self(Arc::new(Inner {
|
|
world,
|
|
runtime,
|
|
journal: Arc::new(journal),
|
|
state: Mutex::new(State {
|
|
processes: HashMap::new(),
|
|
locks: HashMap::new(),
|
|
native: HashMap::new(),
|
|
services: Vec::new(),
|
|
}),
|
|
notifications: tokio::sync::broadcast::channel(1024).0,
|
|
exit: watch::channel(false).0,
|
|
components: components::Components::default(),
|
|
admission: tokio::sync::RwLock::new(()),
|
|
shutdown: std::sync::atomic::AtomicBool::new(false),
|
|
run: sausage_vfs::Memory::default(),
|
|
graph: Mutex::new(crate::graph::Graph::default()),
|
|
activations: tokio::sync::Mutex::new(()),
|
|
watcher: Mutex::new(None),
|
|
retirements: Mutex::new(std::collections::HashSet::new()),
|
|
}))
|
|
}
|
|
/// Register a native plugin for subsequent calls.
|
|
pub fn register_native(&self, name: &str, plugin: NativePlugin) {
|
|
self.0
|
|
.state
|
|
.lock()
|
|
.unwrap()
|
|
.native
|
|
.insert(name.into(), plugin);
|
|
}
|
|
/// Subscribe to notifications addressed to the console.
|
|
pub fn subscribe(&self) -> tokio::sync::broadcast::Receiver<Value> {
|
|
self.0.notifications.subscribe()
|
|
}
|
|
/// Snapshot the metadata of each process that has not ended.
|
|
pub fn processes(&self) -> HashMap<String, ProcessInfo> {
|
|
self.0
|
|
.state
|
|
.lock()
|
|
.unwrap()
|
|
.processes
|
|
.iter()
|
|
.map(|(p, s)| (p.clone(), s.info.clone()))
|
|
.collect()
|
|
}
|
|
/// Make a console-side call and wait for its JSON result.
|
|
pub async fn call(&self, method: &str, params: Value) -> RpcResult {
|
|
self.route(
|
|
"console".into(),
|
|
Some(json!(Uuid::now_v7().to_string())),
|
|
method.into(),
|
|
params,
|
|
false,
|
|
)
|
|
.await
|
|
}
|
|
/// Wait until a core.exit handler requests a restart.
|
|
pub async fn exit_requested(&self) {
|
|
let mut exit = self.0.exit.subscribe();
|
|
let _ = exit.wait_for(|requested| *requested).await;
|
|
}
|
|
/// Return whether a core.exit handler requested a restart.
|
|
pub fn restart_requested(&self) -> bool {
|
|
*self.0.exit.borrow()
|
|
}
|
|
}
|