Skip to content

Commit

Permalink
Merge pull request #2692 from lann/factors-executor
Browse files Browse the repository at this point in the history
Add `spin-factors-executor`
  • Loading branch information
lann committed Jul 30, 2024
2 parents 3c087c3 + 3782a36 commit 8a88d81
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 24 deletions.
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions crates/core/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// Enable spin-factors-derive to emit expanded macro output.
let out_dir = std::env::var("OUT_DIR").unwrap();
println!("cargo:rustc-env=SPIN_FACTORS_DERIVE_EXPAND_DIR={out_dir}");
}
5 changes: 2 additions & 3 deletions crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,16 @@ use std::{path::PathBuf, time::Duration};
use anyhow::Result;
use crossbeam_channel::Sender;
use tracing::instrument;
use wasmtime::component::{InstancePre, Linker};
use wasmtime::{InstanceAllocationStrategy, PoolingAllocationConfig};

pub use async_trait::async_trait;
pub use wasmtime::{
self,
component::{Component, Instance},
component::{Component, Instance, InstancePre, Linker},
Instance as ModuleInstance, Module, Trap,
};

pub use store::{Store, StoreBuilder};
pub use store::{AsState, Store, StoreBuilder};

/// The default [`EngineBuilder::epoch_tick_interval`].
pub const DEFAULT_EPOCH_TICK_INTERVAL: Duration = Duration::from_millis(10);
Expand Down
19 changes: 16 additions & 3 deletions crates/core/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,11 @@ impl StoreBuilder {
///
/// The `T` parameter must provide access to a [`State`] via `impl
/// AsMut<State>`.
pub fn build<T: AsMut<State>>(self, mut data: T) -> Result<Store<T>> {
data.as_mut().store_limits = self.store_limits;
pub fn build<T: AsState>(self, mut data: T) -> Result<Store<T>> {
data.as_state().store_limits = self.store_limits;

let mut inner = wasmtime::Store::new(&self.engine, data);
inner.limiter_async(|data| &mut data.as_mut().store_limits);
inner.limiter_async(|data| &mut data.as_state().store_limits);

// With epoch interruption enabled, there must be _some_ deadline set
// or execution will trap immediately. Since this is a delta, we need
Expand All @@ -115,3 +115,16 @@ impl StoreBuilder {
})
}
}

/// For consumers that need to use a type other than [`State`] as the [`Store`]
/// `data`, this trait must be implemented for that type.
pub trait AsState {
/// Gives access to the inner [`State`].
fn as_state(&mut self) -> &mut State;
}

impl AsState for State {
fn as_state(&mut self) -> &mut State {
self
}
}
12 changes: 6 additions & 6 deletions crates/core/tests/integration_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use std::{

use anyhow::Context;
use serde_json::json;
use spin_core::{Component, Config, Engine, State, Store, StoreBuilder, Trap};
use spin_core::{AsState, Component, Config, Engine, State, Store, StoreBuilder, Trap};
use spin_factor_wasi::{DummyFilesMounter, WasiFactor};
use spin_factors::{App, RuntimeFactors};
use spin_factors::{App, AsInstanceState, RuntimeFactors};
use spin_locked_app::locked::LockedApp;
use tokio::{fs, io::AsyncWrite};
use wasmtime_wasi::I32Exit;
Expand Down Expand Up @@ -93,14 +93,14 @@ struct TestState {
factors: TestFactorsInstanceState,
}

impl AsMut<State> for TestState {
fn as_mut(&mut self) -> &mut State {
impl AsState for TestState {
fn as_state(&mut self) -> &mut State {
&mut self.core
}
}

impl AsMut<TestFactorsInstanceState> for TestState {
fn as_mut(&mut self) -> &mut TestFactorsInstanceState {
impl AsInstanceState<TestFactorsInstanceState> for TestState {
fn as_instance_state(&mut self) -> &mut TestFactorsInstanceState {
&mut self.factors
}
}
Expand Down
10 changes: 5 additions & 5 deletions crates/factors-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn expand_factors(input: &DeriveInput) -> syn::Result<TokenStream> {
type InstanceState = #state_name;
type RuntimeConfig = #runtime_config_name;

fn init<T: AsMut<Self::InstanceState> + Send + 'static>(
fn init<T: #factors_path::AsInstanceState<Self::InstanceState> + Send + 'static>(
&mut self,
linker: &mut #wasmtime::component::Linker<T>,
) -> #Result<()> {
Expand All @@ -98,9 +98,9 @@ fn expand_factors(input: &DeriveInput) -> syn::Result<TokenStream> {
&mut self.#factor_names,
#factors_path::InitContext::<T, #factor_types>::new(
linker,
|data| &mut data.as_mut().#factor_names,
|data| &mut data.as_instance_state().#factor_names,
|data| {
let state = data.as_mut();
let state = data.as_instance_state();
(&mut state.#factor_names, &mut state.__table)
},
)
Expand Down Expand Up @@ -239,8 +239,8 @@ fn expand_factors(input: &DeriveInput) -> syn::Result<TokenStream> {
}
}

impl AsMut<#state_name> for #state_name {
fn as_mut(&mut self) -> &mut Self {
impl #factors_path::AsInstanceState<#state_name> for #state_name {
fn as_instance_state(&mut self) -> &mut Self {
self
}
}
Expand Down
23 changes: 23 additions & 0 deletions crates/factors-executor/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "spin-factors-executor"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true

[dependencies]
anyhow = "1"
spin-app = { path = "../app" }
spin-core = { path = "../core" }
spin-factors = { path = "../factors" }

[dev-dependencies]
spin-factor-wasi = { path = "../factor-wasi" }
spin-factors-test = { path = "../factors-test" }
tokio = { version = "1", features = ["macros", "rt"] }

[lints]
workspace = true
223 changes: 223 additions & 0 deletions crates/factors-executor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
use std::collections::HashMap;

use anyhow::Context;
use spin_app::{App, AppComponent};
use spin_core::Component;
use spin_factors::{AsInstanceState, ConfiguredApp, RuntimeFactors, RuntimeFactorsInstanceState};

/// A FactorsExecutor manages execution of a Spin app.
///
/// `Factors` is the executor's [`RuntimeFactors`]. `ExecutorInstanceState`
/// holds any other per-instance state needed by the caller.
pub struct FactorsExecutor<T: RuntimeFactors, U = ()> {
factors: T,
core_engine: spin_core::Engine<InstanceState<T::InstanceState, U>>,
configured_app: ConfiguredApp<T>,
// Maps component IDs -> InstancePres
component_instance_pres: HashMap<String, InstancePre<T, U>>,
}

type InstancePre<T, U> =
spin_core::InstancePre<InstanceState<<T as RuntimeFactors>::InstanceState, U>>;

impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutor<T, U> {
/// Constructs a new executor.
pub fn new(
core_config: &spin_core::Config,
mut factors: T,
app: App,
mut component_loader: impl ComponentLoader,
runtime_config: T::RuntimeConfig,
) -> anyhow::Result<Self> {
let core_engine = {
let mut builder =
spin_core::Engine::builder(core_config).context("failed to initialize engine")?;
factors
.init(builder.linker())
.context("failed to initialize factors")?;
builder.build()
};

let configured_app = factors
.configure_app(app, runtime_config)
.context("failed to configure app")?;

let component_instance_pres = configured_app
.app()
.components()
.map(|app_component| {
let component =
component_loader.load_component(core_engine.as_ref(), &app_component)?;
let instance_pre = core_engine.instantiate_pre(&component)?;
Ok((app_component.id().to_string(), instance_pre))
})
.collect::<anyhow::Result<HashMap<_, _>>>()?;

Ok(Self {
factors,
core_engine,
configured_app,
component_instance_pres,
})
}

/// Returns an instance builder for the given component ID.
pub fn prepare(&mut self, component_id: &str) -> anyhow::Result<FactorsInstanceBuilder<T, U>> {
let app_component = self
.configured_app
.app()
.get_component(component_id)
.with_context(|| format!("no such component {component_id:?}"))?;
let instance_pre = self.component_instance_pres.get(component_id).unwrap();
let factor_builders = self.factors.prepare(&self.configured_app, component_id)?;
let store_builder = self.core_engine.store_builder();
Ok(FactorsInstanceBuilder {
store_builder,
factor_builders,
instance_pre,
app_component,
factors: &self.factors,
})
}
}

/// A ComponentLoader is responsible for loading Wasmtime [`Component`]s.
pub trait ComponentLoader {
/// Loads a [`Component`] for the given [`AppComponent`].
fn load_component(
&mut self,
engine: &spin_core::wasmtime::Engine,
component: &AppComponent,
) -> anyhow::Result<Component>;
}

/// A FactorsInstanceBuilder manages the instantiation of a Spin component
/// instance.
pub struct FactorsInstanceBuilder<'a, T: RuntimeFactors, U> {
app_component: AppComponent<'a>,
store_builder: spin_core::StoreBuilder,
factor_builders: T::InstanceBuilders,
instance_pre: &'a InstancePre<T, U>,
factors: &'a T,
}

impl<'a, T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'a, T, U> {
/// Returns the app component for the instance.
pub fn app_component(&self) -> &AppComponent {
&self.app_component
}

/// Returns the store builder for the instance.
pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
&mut self.store_builder
}

/// Returns the factor instance builders for the instance.
pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
&mut self.factor_builders
}

/// Instantiates the instance with the given executor instance state
pub async fn instantiate(
self,
executor_instance_state: U,
) -> anyhow::Result<(
spin_core::Instance,
spin_core::Store<InstanceState<T::InstanceState, U>>,
)> {
let instance_state = InstanceState {
core: Default::default(),
factors: self.factors.build_instance_state(self.factor_builders)?,
executor: executor_instance_state,
};
let mut store = self.store_builder.build(instance_state)?;
let instance = self.instance_pre.instantiate_async(&mut store).await?;
Ok((instance, store))
}
}

/// InstanceState is the [`spin_core::Store`] `data` for an instance.
pub struct InstanceState<FactorsState, ExecutorInstanceState> {
core: spin_core::State,
factors: FactorsState,
executor: ExecutorInstanceState,
}

impl<T, U> InstanceState<T, U> {
/// Provides access to the `ExecutorInstanceState`.
pub fn executor_instance_state(&mut self) -> &mut U {
&mut self.executor
}
}

impl<T, U> spin_core::AsState for InstanceState<T, U> {
fn as_state(&mut self) -> &mut spin_core::State {
&mut self.core
}
}

impl<T: RuntimeFactorsInstanceState, U> AsInstanceState<T> for InstanceState<T, U> {
fn as_instance_state(&mut self) -> &mut T {
&mut self.factors
}
}

#[cfg(test)]
mod tests {
use spin_factor_wasi::{DummyFilesMounter, WasiFactor};
use spin_factors::RuntimeFactors;
use spin_factors_test::TestEnvironment;

use super::*;

#[derive(RuntimeFactors)]
struct TestFactors {
wasi: WasiFactor,
}

#[tokio::test]
async fn instance_builder_works() -> anyhow::Result<()> {
let factors = TestFactors {
wasi: WasiFactor::new(DummyFilesMounter),
};
let env = TestEnvironment::new(factors);
let locked = env.build_locked_app().await?;
let app = App::new("test-app", locked);

let mut executor = FactorsExecutor::new(
&Default::default(),
env.factors,
app,
DummyComponentLoader,
Default::default(),
)?;

let mut instance_builder = executor.prepare("empty")?;

assert_eq!(instance_builder.app_component().id(), "empty");

instance_builder.store_builder().max_memory_size(1_000_000);

instance_builder
.factor_builders()
.wasi
.as_mut()
.unwrap()
.args(["foo"]);

let (_instance, _store) = instance_builder.instantiate(()).await?;
Ok(())
}

struct DummyComponentLoader;

impl ComponentLoader for DummyComponentLoader {
fn load_component(
&mut self,
engine: &spin_core::wasmtime::Engine,
_component: &AppComponent,
) -> anyhow::Result<Component> {
Component::new(engine, "(component)")
}
}
}
2 changes: 1 addition & 1 deletion crates/factors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub use crate::{
factor::{ConfigureAppContext, ConfiguredApp, Factor, FactorInstanceState, InitContext},
prepare::{FactorInstanceBuilder, InstanceBuilders, PrepareContext, SelfInstanceBuilder},
runtime_config::{FactorRuntimeConfigSource, RuntimeConfigSourceFinalizer},
runtime_factors::{RuntimeFactors, RuntimeFactorsInstanceState},
runtime_factors::{AsInstanceState, RuntimeFactors, RuntimeFactorsInstanceState},
};

/// Result wrapper type defaulting to use [`Error`].
Expand Down
Loading

0 comments on commit 8a88d81

Please sign in to comment.