Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ tracing = "0.1"
tracing-core = { version = "0.1", default-features = false, features = ["std"] }
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "env-filter", "ansi", "json"] }

# `strum::IntoStaticStr` on every error / event enum gives a free
# `strum::IntoStaticStr` on error / event enums gives a free
# snake_case `&'static str` for every variant, which feeds directly
# into `metrics::counter!(..., "error_kind" => name)` and
# `tracing::warn!(error_kind = name, ...)` recordings without an
Expand Down
5 changes: 2 additions & 3 deletions crates/nexum-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ thiserror.workspace = true
async-trait.workspace = true
# Newtype boilerplate (`Display`, `AsRef`, `From`) for identity wrappers.
derive_more.workspace = true
# `strum::IntoStaticStr` on the pub error enums: a snake_case
# `&'static str` per variant, published for consumers' metric labels;
# nothing in-crate consumes the conversion.
# `strum::IntoStaticStr` on `LogSource`: the snake_case variant name is
# the tracing `source` field.
strum.workspace = true
tokio.workspace = true
# Task lifecycle and graceful shutdown; the sole crate that raw-spawns
Expand Down
114 changes: 48 additions & 66 deletions crates/nexum-runtime/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@

use std::future::IntoFuture;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;

use nexum_tasks::{DrainOutcome, TaskExit, TaskHandle, TaskManager, TaskSet};
use tracing::{error, info, warn};
use wasmtime::Engine;

use crate::addons::{AddOnHandle, AddOns, AddOnsContext, RuntimeAddOn};
use crate::addons::{AddOnHandle, AddOns, AddOnsContext};
use crate::engine_config::{EngineConfig, ModuleEntry};
use crate::host::component::{
BuilderContext, ComponentBuilder, Components, ComponentsBuilder, RuntimeTypes,
Expand Down Expand Up @@ -123,23 +123,23 @@ pub(crate) fn wasmtime_config() -> wasmtime::Config {

/// A fully-assembled runtime: concrete backends, extensions, add-ons, and the
/// optional module-source override. [`launch`](Self::launch) runs it.
pub struct AssembledRuntime<'a, T: RuntimeTypes> {
pub struct AssembledRuntime<T: RuntimeTypes> {
/// Shared backends threaded into every module store.
pub components: Components<T>,
/// Extensions: namespaces, capabilities, linker hooks, services, and
/// provider kinds.
pub extensions: Vec<Arc<dyn Extension<T>>>,
/// Cross-cutting facilities installed before the engine boots.
pub add_ons: &'a [&'a dyn RuntimeAddOn],
pub add_ons: AddOns,
/// Single-module source override; `None` runs `[[modules]]`.
pub wasm: Option<&'a Path>,
pub wasm: Option<PathBuf>,
/// Manifest paired with `wasm`.
pub manifest: Option<&'a Path>,
pub manifest: Option<PathBuf>,
/// Per-store WASI clock override; `None` leaves the ambient host clocks.
pub clocks: Option<WasiClockOverride>,
}

impl<T: RuntimeTypes> AssembledRuntime<'_, T> {
impl<T: RuntimeTypes> AssembledRuntime<T> {
/// Run the imperative launch sequence and return the running handle.
pub async fn launch(self, ctx: LaunchContext<'_>) -> anyhow::Result<RuntimeHandle> {
let AssembledRuntime {
Expand Down Expand Up @@ -184,8 +184,8 @@ impl<T: RuntimeTypes> AssembledRuntime<'_, T> {
);
}
let entry = ModuleEntry {
path: wasm.to_path_buf(),
manifest: manifest.map(Path::to_path_buf),
path: wasm,
manifest,
};
Supervisor::boot_single(
&engine,
Expand Down Expand Up @@ -347,20 +347,19 @@ impl<T: RuntimeTypes> AssembledRuntime<'_, T> {

/// Opens the backends with a fresh [`TaskManager`], then drives
/// [`AssembledRuntime::launch`]; the shared tail of every terminal stage.
async fn open_and_launch<T, C, S, E, L>(
async fn open_and_launch<T, C, S, L>(
config: &EngineConfig,
extensions: Vec<Arc<dyn Extension<T>>>,
add_ons: &[&dyn RuntimeAddOn],
wasm: Option<&Path>,
manifest: Option<&Path>,
add_ons: AddOns,
wasm: Option<PathBuf>,
manifest: Option<PathBuf>,
clocks: Option<WasiClockOverride>,
components: ComponentsBuilder<C, S, E, L>,
components: ComponentsBuilder<C, S, L>,
) -> anyhow::Result<RuntimeHandle>
where
T: RuntimeTypes,
C: ComponentBuilder<Output = ProviderPool>,
S: ComponentBuilder<Output = T::Store>,
E: ComponentBuilder<Output = T::Ext>,
L: ComponentBuilder<Output = LogPipeline>,
{
let tasks = TaskManager::new();
Expand Down Expand Up @@ -466,12 +465,12 @@ impl<'a, R: Runtime> PresetBuilder<'a, R> {
/// Override the preset's component builders before launch; `map` swaps one
/// seam while the preset's extensions and add-ons carry through. Mirror of
/// [`TypedBuilder::with_components`].
pub fn with_components<C, S, E, L>(
pub fn with_components<C, S, L>(
self,
map: impl FnOnce(
ComponentsBuilder<R::ChainBuilder, R::StoreBuilder, R::ExtBuilder, R::LogsBuilder>,
) -> ComponentsBuilder<C, S, E, L>,
) -> PresetComponentsBuilder<'a, R::Types, C, S, E, L> {
ComponentsBuilder<R::ChainBuilder, R::StoreBuilder, R::LogsBuilder>,
) -> ComponentsBuilder<C, S, L>,
) -> PresetComponentsBuilder<'a, R::Types, C, S, L> {
// Gather the preset's extensions and add-ons before `components`
// consumes the preset by value.
let mut extensions = self.preset.extensions(self.config);
Expand Down Expand Up @@ -502,16 +501,13 @@ impl<'a, R: Runtime> PresetBuilder<'a, R> {
} = self;
let mut extensions = preset.extensions(config);
extensions.extend(appended);
// `add_ons` owns the boxed add-ons; `add_on_refs` borrows into it and is
// consumed by the launch call, so both must stay in scope for that call.
let add_ons = preset.add_ons();
let add_on_refs: Vec<&dyn RuntimeAddOn> = add_ons.iter().map(|a| &**a).collect();
open_and_launch(
config,
extensions,
&add_on_refs,
wasm.as_deref(),
manifest.as_deref(),
add_ons,
wasm,
manifest,
clocks,
preset.components(),
)
Expand All @@ -521,36 +517,32 @@ impl<'a, R: Runtime> PresetBuilder<'a, R> {

/// A preset with its component builders overridden through
/// [`PresetBuilder::with_components`], leaving only [`launch`](Self::launch).
pub struct PresetComponentsBuilder<'a, T: RuntimeTypes, C, S, E, L> {
pub struct PresetComponentsBuilder<'a, T: RuntimeTypes, C, S, L> {
config: &'a EngineConfig,
extensions: Vec<Arc<dyn Extension<T>>>,
add_ons: AddOns,
wasm: Option<PathBuf>,
manifest: Option<PathBuf>,
clocks: Option<WasiClockOverride>,
components: ComponentsBuilder<C, S, E, L>,
components: ComponentsBuilder<C, S, L>,
}

impl<T, C, S, E, L> PresetComponentsBuilder<'_, T, C, S, E, L>
impl<T, C, S, L> PresetComponentsBuilder<'_, T, C, S, L>
where
T: RuntimeTypes,
C: ComponentBuilder<Output = ProviderPool>,
S: ComponentBuilder<Output = T::Store>,
E: ComponentBuilder<Output = T::Ext>,
L: ComponentBuilder<Output = LogPipeline>,
{
/// Open the overridden backends and launch, otherwise as
/// [`PresetBuilder::launch`].
pub async fn launch(self) -> anyhow::Result<RuntimeHandle> {
// `add_ons` owns the boxed add-ons; `add_on_refs` borrows into it and is
// consumed by the launch call, so both must stay in scope for that call.
let add_on_refs: Vec<&dyn RuntimeAddOn> = self.add_ons.iter().map(|a| &**a).collect();
open_and_launch(
self.config,
self.extensions,
&add_on_refs,
self.wasm.as_deref(),
self.manifest.as_deref(),
self.add_ons,
self.wasm,
self.manifest,
self.clocks,
self.components,
)
Expand Down Expand Up @@ -595,49 +587,48 @@ impl<'a, T: RuntimeTypes> TypedBuilder<'a, T> {
}

/// Bind the component builders that open the backends at launch.
pub fn with_components<C, S, E, L>(
pub fn with_components<C, S, L>(
self,
components: ComponentsBuilder<C, S, E, L>,
) -> ReadyBuilder<'a, T, C, S, E, L> {
components: ComponentsBuilder<C, S, L>,
) -> ReadyBuilder<'a, T, C, S, L> {
ReadyBuilder {
config: self.config,
extensions: self.extensions,
wasm: self.wasm,
manifest: self.manifest,
clocks: self.clocks,
components,
add_ons: &[],
add_ons: AddOns::new(),
}
}
}

/// The assembly is complete; [`launch`](Self::launch) opens the backends and
/// runs.
pub struct ReadyBuilder<'a, T: RuntimeTypes, C, S, E, L> {
pub struct ReadyBuilder<'a, T: RuntimeTypes, C, S, L> {
config: &'a EngineConfig,
extensions: Vec<Arc<dyn Extension<T>>>,
wasm: Option<PathBuf>,
manifest: Option<PathBuf>,
clocks: Option<WasiClockOverride>,
components: ComponentsBuilder<C, S, E, L>,
add_ons: &'a [&'a dyn RuntimeAddOn],
components: ComponentsBuilder<C, S, L>,
add_ons: AddOns,
}

impl<'a, T: RuntimeTypes, C, S, E, L> ReadyBuilder<'a, T, C, S, E, L> {
impl<T: RuntimeTypes, C, S, L> ReadyBuilder<'_, T, C, S, L> {
/// Bind the cross-cutting add-on set installed before the engine boots;
/// defaults to none.
pub fn with_add_ons(mut self, add_ons: &'a [&'a dyn RuntimeAddOn]) -> Self {
pub fn with_add_ons(mut self, add_ons: AddOns) -> Self {
self.add_ons = add_ons;
self
}
}

impl<T, C, S, E, L> ReadyBuilder<'_, T, C, S, E, L>
impl<T, C, S, L> ReadyBuilder<'_, T, C, S, L>
where
T: RuntimeTypes,
C: ComponentBuilder<Output = ProviderPool>,
S: ComponentBuilder<Output = T::Store>,
E: ComponentBuilder<Output = T::Ext>,
L: ComponentBuilder<Output = LogPipeline>,
{
/// Open the backends and launch, driving [`AssembledRuntime::launch`]
Expand All @@ -647,8 +638,8 @@ where
self.config,
self.extensions,
self.add_ons,
self.wasm.as_deref(),
self.manifest.as_deref(),
self.wasm,
self.manifest,
self.clocks,
self.components,
)
Expand All @@ -663,7 +654,7 @@ mod tests {
use std::time::{SystemTime, UNIX_EPOCH};

use super::*;
use crate::addons::AddOns;
use crate::addons::{AddOns, RuntimeAddOn};
use crate::engine_config::EngineConfig;
use crate::host::component::{LocalStoreBuilder, LogPipelineBuilder, ProviderPoolBuilder};
use crate::host::extension::HostWallClock;
Expand Down Expand Up @@ -728,11 +719,10 @@ mod tests {
type Types = CoreRuntime;
type ChainBuilder = ProviderPoolBuilder;
type StoreBuilder = LocalStoreBuilder;
type ExtBuilder = ();
type LogsBuilder = LogPipelineBuilder;

fn components(self) -> ComponentsBuilder<ProviderPoolBuilder, LocalStoreBuilder, ()> {
ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ())
fn components(self) -> ComponentsBuilder<ProviderPoolBuilder, LocalStoreBuilder> {
ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder)
}

fn add_ons(&self) -> AddOns {
Expand Down Expand Up @@ -827,7 +817,6 @@ mod tests {
.with_components(ComponentsBuilder::new(
ProviderPoolBuilder,
LocalStoreBuilder,
(),
))
.launch()
.await
Expand Down Expand Up @@ -881,14 +870,13 @@ mod tests {
type Types = CoreRuntime;
type ChainBuilder = ProviderPoolBuilder;
type StoreBuilder = LocalStoreBuilder;
type ExtBuilder = ();
type LogsBuilder = Prebuilt<LogPipeline>;

fn components(
self,
) -> ComponentsBuilder<ProviderPoolBuilder, LocalStoreBuilder, (), Prebuilt<LogPipeline>>
) -> ComponentsBuilder<ProviderPoolBuilder, LocalStoreBuilder, Prebuilt<LogPipeline>>
{
ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ())
ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder)
.with_logs(Prebuilt(self.logs))
}

Expand Down Expand Up @@ -935,11 +923,10 @@ mod tests {
type Types = CoreRuntime;
type ChainBuilder = ProviderPoolBuilder;
type StoreBuilder = LocalStoreBuilder;
type ExtBuilder = ();
type LogsBuilder = LogPipelineBuilder;

fn components(self) -> ComponentsBuilder<ProviderPoolBuilder, LocalStoreBuilder, ()> {
ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ())
fn components(self) -> ComponentsBuilder<ProviderPoolBuilder, LocalStoreBuilder> {
ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder)
}

fn add_ons(&self) -> AddOns {
Expand Down Expand Up @@ -1050,7 +1037,6 @@ mod tests {
.with_components(ComponentsBuilder::new(
ProviderPoolBuilder,
LocalStoreBuilder,
(),
))
.launch()
.await
Expand Down Expand Up @@ -1083,7 +1069,6 @@ mod tests {
.with_components(ComponentsBuilder::new(
ProviderPoolBuilder,
LocalStoreBuilder,
(),
))
.launch()
.await
Expand Down Expand Up @@ -1122,18 +1107,16 @@ mod tests {
data_dir: &data_dir,
executor: &executor,
};
let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder, ())
let components = ComponentsBuilder::new(ProviderPoolBuilder, LocalStoreBuilder)
.build::<CoreRuntime>(&build_ctx)
.await
.expect("build core components");

let calls = Arc::new(AtomicUsize::new(0));
let add_on = CountingAddOn(calls.clone());
let add_on_refs: Vec<&dyn RuntimeAddOn> = vec![&add_on];
let runtime = AssembledRuntime {
components,
extensions: Vec::new(),
add_ons: &add_on_refs,
add_ons: vec![Box::new(CountingAddOn(calls.clone()))],
wasm: None,
manifest: None,
clocks: None,
Expand Down Expand Up @@ -1174,7 +1157,6 @@ mod tests {
.with_components(ComponentsBuilder::new(
ProviderPoolBuilder,
LocalStoreBuilder,
(),
))
.launch()
.await
Expand Down
4 changes: 1 addition & 3 deletions crates/nexum-runtime/src/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::path::PathBuf;
use std::str::FromStr;

use sha2::{Digest, Sha256};
use strum::IntoStaticStr;
use thiserror::Error;

const SCHEME: &str = "sha256";
Expand Down Expand Up @@ -64,8 +63,7 @@ impl fmt::Display for ContentDigest {
}
}

#[derive(Debug, Error, IntoStaticStr)]
#[strum(serialize_all = "snake_case")]
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DigestParseError {
/// No `scheme:` prefix; the empty string lands here too.
Expand Down
Loading
Loading