spin_factors_executor/
lib.rs

1use std::{collections::HashMap, sync::Arc};
2
3use anyhow::Context;
4use spin_app::{App, AppComponent};
5use spin_core::{async_trait, Component};
6use spin_factors::{
7    AsInstanceState, ConfiguredApp, Factor, HasInstanceBuilder, RuntimeFactors,
8    RuntimeFactorsInstanceState,
9};
10
11/// A FactorsExecutor manages execution of a Spin app.
12///
13/// It is generic over the executor's [`RuntimeFactors`]. Additionally, it
14/// holds any other per-instance state needed by the caller.
15pub struct FactorsExecutor<T: RuntimeFactors, U = ()> {
16    core_engine: spin_core::Engine<InstanceState<T::InstanceState, U>>,
17    factors: T,
18    hooks: Vec<Box<dyn ExecutorHooks<T, U>>>,
19}
20
21impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutor<T, U> {
22    /// Constructs a new executor.
23    pub fn new(
24        mut core_engine_builder: spin_core::EngineBuilder<
25            InstanceState<<T as RuntimeFactors>::InstanceState, U>,
26        >,
27        mut factors: T,
28    ) -> anyhow::Result<Self> {
29        factors
30            .init(core_engine_builder.linker())
31            .context("failed to initialize factors")?;
32        Ok(Self {
33            factors,
34            core_engine: core_engine_builder.build(),
35            hooks: Default::default(),
36        })
37    }
38
39    pub fn core_engine(&self) -> &spin_core::Engine<InstanceState<T::InstanceState, U>> {
40        &self.core_engine
41    }
42
43    // Adds the given [`ExecutorHooks`] to this executor.
44    ///
45    /// Hooks are run in the order they are added.
46    pub fn add_hooks(&mut self, hooks: impl ExecutorHooks<T, U> + 'static) {
47        self.hooks.push(Box::new(hooks));
48    }
49
50    /// Loads a [`App`] with this executor.
51    pub async fn load_app(
52        self: Arc<Self>,
53        app: App,
54        runtime_config: T::RuntimeConfig,
55        component_loader: &impl ComponentLoader<T, U>,
56    ) -> anyhow::Result<FactorsExecutorApp<T, U>> {
57        let configured_app = self
58            .factors
59            .configure_app(app, runtime_config)
60            .context("failed to configure app")?;
61
62        for hooks in &self.hooks {
63            hooks.configure_app(&configured_app).await?;
64        }
65
66        let components = configured_app.app().components();
67        let mut component_instance_pres = HashMap::with_capacity(components.len());
68
69        for component in components {
70            let instance_pre = component_loader
71                .load_instance_pre(&self.core_engine, &component)
72                .await?;
73            component_instance_pres.insert(component.id().to_string(), instance_pre);
74        }
75
76        Ok(FactorsExecutorApp {
77            executor: self.clone(),
78            configured_app,
79            component_instance_pres,
80        })
81    }
82}
83
84#[async_trait]
85pub trait ExecutorHooks<T, U>: Send + Sync
86where
87    T: RuntimeFactors,
88{
89    /// Configure app hooks run immediately after [`RuntimeFactors::configure_app`].
90    async fn configure_app(&self, configured_app: &ConfiguredApp<T>) -> anyhow::Result<()> {
91        let _ = configured_app;
92        Ok(())
93    }
94
95    /// Prepare instance hooks run immediately before [`FactorsExecutorApp::prepare`] returns.
96    fn prepare_instance(&self, builder: &mut FactorsInstanceBuilder<T, U>) -> anyhow::Result<()> {
97        let _ = builder;
98        Ok(())
99    }
100}
101
102/// A ComponentLoader is responsible for loading Wasmtime [`Component`]s.
103#[async_trait]
104pub trait ComponentLoader<T: RuntimeFactors, U>: Sync {
105    /// Loads a [`Component`] for the given [`AppComponent`].
106    async fn load_component(
107        &self,
108        engine: &spin_core::wasmtime::Engine,
109        component: &AppComponent,
110    ) -> anyhow::Result<Component>;
111
112    /// Loads [`InstancePre`] for the given [`AppComponent`].
113    async fn load_instance_pre(
114        &self,
115        engine: &spin_core::Engine<InstanceState<T::InstanceState, U>>,
116        component: &AppComponent,
117    ) -> anyhow::Result<spin_core::InstancePre<InstanceState<T::InstanceState, U>>> {
118        let component = self.load_component(engine.as_ref(), component).await?;
119        engine.instantiate_pre(&component)
120    }
121}
122
123type InstancePre<T, U> =
124    spin_core::InstancePre<InstanceState<<T as RuntimeFactors>::InstanceState, U>>;
125
126/// A FactorsExecutorApp represents a loaded Spin app, ready for instantiation.
127///
128/// It is generic over the executor's [`RuntimeFactors`] and any ad-hoc additional
129/// per-instance state needed by the caller.
130pub struct FactorsExecutorApp<T: RuntimeFactors, U> {
131    executor: Arc<FactorsExecutor<T, U>>,
132    configured_app: ConfiguredApp<T>,
133    // Maps component IDs -> InstancePres
134    component_instance_pres: HashMap<String, InstancePre<T, U>>,
135}
136
137impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U> {
138    pub fn engine(&self) -> &spin_core::Engine<InstanceState<T::InstanceState, U>> {
139        &self.executor.core_engine
140    }
141
142    pub fn configured_app(&self) -> &ConfiguredApp<T> {
143        &self.configured_app
144    }
145
146    pub fn app(&self) -> &App {
147        self.configured_app.app()
148    }
149
150    pub fn get_component(&self, component_id: &str) -> anyhow::Result<&Component> {
151        let instance_pre = self
152            .component_instance_pres
153            .get(component_id)
154            .with_context(|| format!("no such component {component_id:?}"))?;
155        Ok(instance_pre.component())
156    }
157
158    /// Returns an instance builder for the given component ID.
159    pub fn prepare(&self, component_id: &str) -> anyhow::Result<FactorsInstanceBuilder<T, U>> {
160        let app_component = self
161            .configured_app
162            .app()
163            .get_component(component_id)
164            .with_context(|| format!("no such component {component_id:?}"))?;
165
166        let instance_pre = self.component_instance_pres.get(component_id).unwrap();
167
168        let factor_builders = self
169            .executor
170            .factors
171            .prepare(&self.configured_app, component_id)?;
172
173        let store_builder = self.executor.core_engine.store_builder();
174
175        let mut builder = FactorsInstanceBuilder {
176            store_builder,
177            factor_builders,
178            instance_pre,
179            app_component,
180            factors: &self.executor.factors,
181        };
182
183        for hooks in &self.executor.hooks {
184            hooks.prepare_instance(&mut builder)?;
185        }
186
187        Ok(builder)
188    }
189}
190
191/// A FactorsInstanceBuilder manages the instantiation of a Spin component instance.
192///
193/// It is generic over the executor's [`RuntimeFactors`] and any ad-hoc additional
194/// per-instance state needed by the caller.
195pub struct FactorsInstanceBuilder<'a, F: RuntimeFactors, U> {
196    app_component: AppComponent<'a>,
197    store_builder: spin_core::StoreBuilder,
198    factor_builders: F::InstanceBuilders,
199    instance_pre: &'a InstancePre<F, U>,
200    factors: &'a F,
201}
202
203impl<T: RuntimeFactors, U> FactorsInstanceBuilder<'_, T, U> {
204    /// Returns the app component for the instance.
205    pub fn app_component(&self) -> &AppComponent {
206        &self.app_component
207    }
208
209    /// Returns the store builder for the instance.
210    pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
211        &mut self.store_builder
212    }
213
214    /// Returns the factor instance builders for the instance.
215    pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
216        &mut self.factor_builders
217    }
218
219    /// Returns the specific instance builder for the given factor.
220    pub fn factor_builder<F: Factor>(&mut self) -> Option<&mut F::InstanceBuilder> {
221        self.factor_builders().for_factor::<F>()
222    }
223
224    /// Returns the underlying wasmtime engine for the instance.
225    pub fn wasmtime_engine(&self) -> &spin_core::WasmtimeEngine {
226        self.instance_pre.engine()
227    }
228
229    /// Returns the compiled component for the instance.
230    pub fn component(&self) -> &Component {
231        self.instance_pre.component()
232    }
233}
234
235impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
236    /// Instantiates the instance with the given executor instance state
237    pub async fn instantiate(
238        self,
239        executor_instance_state: U,
240    ) -> anyhow::Result<(
241        spin_core::Instance,
242        spin_core::Store<InstanceState<T::InstanceState, U>>,
243    )> {
244        let instance_state = InstanceState {
245            core: Default::default(),
246            factors: self.factors.build_instance_state(self.factor_builders)?,
247            executor: executor_instance_state,
248        };
249        let mut store = self.store_builder.build(instance_state)?;
250        let instance = self.instance_pre.instantiate_async(&mut store).await?;
251        Ok((instance, store))
252    }
253}
254
255/// InstanceState is the [`spin_core::Store`] `data` for an instance.
256///
257/// It is generic over the [`RuntimeFactors::InstanceState`] and any ad-hoc
258/// data needed by the caller.
259pub struct InstanceState<T, U> {
260    core: spin_core::State,
261    factors: T,
262    executor: U,
263}
264
265impl<T, U> InstanceState<T, U> {
266    /// Provides access to the [`spin_core::State`].
267    pub fn core_state(&self) -> &spin_core::State {
268        &self.core
269    }
270
271    /// Provides mutable access to the [`spin_core::State`].
272    pub fn core_state_mut(&mut self) -> &mut spin_core::State {
273        &mut self.core
274    }
275
276    /// Provides access to the [`RuntimeFactors::InstanceState`].
277    pub fn factors_instance_state(&self) -> &T {
278        &self.factors
279    }
280
281    /// Provides mutable access to the [`RuntimeFactors::InstanceState`].
282    pub fn factors_instance_state_mut(&mut self) -> &mut T {
283        &mut self.factors
284    }
285
286    /// Provides access to the ad-hoc executor instance state.
287    pub fn executor_instance_state(&self) -> &U {
288        &self.executor
289    }
290
291    /// Provides mutable access to the ad-hoc executor instance state.
292    pub fn executor_instance_state_mut(&mut self) -> &mut U {
293        &mut self.executor
294    }
295}
296
297impl<T, U> spin_core::AsState for InstanceState<T, U> {
298    fn as_state(&mut self) -> &mut spin_core::State {
299        &mut self.core
300    }
301}
302
303impl<T: RuntimeFactorsInstanceState, U> AsInstanceState<T> for InstanceState<T, U> {
304    fn as_instance_state(&mut self) -> &mut T {
305        &mut self.factors
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use spin_factor_wasi::{DummyFilesMounter, WasiFactor};
312    use spin_factors::RuntimeFactors;
313    use spin_factors_test::TestEnvironment;
314
315    use super::*;
316
317    #[derive(RuntimeFactors)]
318    struct TestFactors {
319        wasi: WasiFactor,
320    }
321
322    #[tokio::test]
323    async fn instance_builder_works() -> anyhow::Result<()> {
324        let factors = TestFactors {
325            wasi: WasiFactor::new(DummyFilesMounter),
326        };
327        let env = TestEnvironment::new(factors);
328        let locked = env.build_locked_app().await?;
329        let app = App::new("test-app", locked);
330
331        let engine_builder = spin_core::Engine::builder(&Default::default())?;
332        let executor = Arc::new(FactorsExecutor::new(engine_builder, env.factors)?);
333
334        let factors_app = executor
335            .load_app(app, Default::default(), &DummyComponentLoader)
336            .await?;
337
338        let mut instance_builder = factors_app.prepare("empty")?;
339
340        assert_eq!(instance_builder.app_component().id(), "empty");
341
342        instance_builder.store_builder().max_memory_size(1_000_000);
343
344        instance_builder
345            .factor_builder::<WasiFactor>()
346            .unwrap()
347            .args(["foo"]);
348
349        let (_instance, _store) = instance_builder.instantiate(()).await?;
350        Ok(())
351    }
352
353    struct DummyComponentLoader;
354
355    #[async_trait]
356    impl ComponentLoader<TestFactors, ()> for DummyComponentLoader {
357        async fn load_component(
358            &self,
359            engine: &spin_core::wasmtime::Engine,
360            _component: &AppComponent,
361        ) -> anyhow::Result<Component> {
362            Component::new(engine, "(component)")
363        }
364    }
365}