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        Ok(self.get_instance_pre(component_id)?.component())
152    }
153
154    pub fn get_instance_pre(&self, component_id: &str) -> anyhow::Result<&InstancePre<T, U>> {
155        self.component_instance_pres
156            .get(component_id)
157            .with_context(|| format!("no such component {component_id:?}"))
158    }
159
160    /// Returns an instance builder for the given component ID.
161    pub fn prepare(&self, component_id: &str) -> anyhow::Result<FactorsInstanceBuilder<T, U>> {
162        let app_component = self
163            .configured_app
164            .app()
165            .get_component(component_id)
166            .with_context(|| format!("no such component {component_id:?}"))?;
167
168        let instance_pre = self.component_instance_pres.get(component_id).unwrap();
169
170        let factor_builders = self
171            .executor
172            .factors
173            .prepare(&self.configured_app, component_id)?;
174
175        let store_builder = self.executor.core_engine.store_builder();
176
177        let mut builder = FactorsInstanceBuilder {
178            store_builder,
179            factor_builders,
180            instance_pre,
181            app_component,
182            factors: &self.executor.factors,
183        };
184
185        for hooks in &self.executor.hooks {
186            hooks.prepare_instance(&mut builder)?;
187        }
188
189        Ok(builder)
190    }
191}
192
193/// A FactorsInstanceBuilder manages the instantiation of a Spin component instance.
194///
195/// It is generic over the executor's [`RuntimeFactors`] and any ad-hoc additional
196/// per-instance state needed by the caller.
197pub struct FactorsInstanceBuilder<'a, F: RuntimeFactors, U> {
198    app_component: AppComponent<'a>,
199    store_builder: spin_core::StoreBuilder,
200    factor_builders: F::InstanceBuilders,
201    instance_pre: &'a InstancePre<F, U>,
202    factors: &'a F,
203}
204
205impl<T: RuntimeFactors, U> FactorsInstanceBuilder<'_, T, U> {
206    /// Returns the app component for the instance.
207    pub fn app_component(&self) -> &AppComponent {
208        &self.app_component
209    }
210
211    /// Returns the store builder for the instance.
212    pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
213        &mut self.store_builder
214    }
215
216    /// Returns the factor instance builders for the instance.
217    pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
218        &mut self.factor_builders
219    }
220
221    /// Returns the specific instance builder for the given factor.
222    pub fn factor_builder<F: Factor>(&mut self) -> Option<&mut F::InstanceBuilder> {
223        self.factor_builders().for_factor::<F>()
224    }
225
226    /// Returns the underlying wasmtime engine for the instance.
227    pub fn wasmtime_engine(&self) -> &spin_core::WasmtimeEngine {
228        self.instance_pre.engine()
229    }
230
231    /// Returns the compiled component for the instance.
232    pub fn component(&self) -> &Component {
233        self.instance_pre.component()
234    }
235}
236
237impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
238    /// Instantiates the instance with the given executor instance state
239    pub async fn instantiate(
240        self,
241        executor_instance_state: U,
242    ) -> anyhow::Result<(
243        spin_core::Instance,
244        spin_core::Store<InstanceState<T::InstanceState, U>>,
245    )> {
246        let instance_state = InstanceState {
247            core: Default::default(),
248            factors: self.factors.build_instance_state(self.factor_builders)?,
249            executor: executor_instance_state,
250        };
251        let mut store = self.store_builder.build(instance_state)?;
252        let instance = self.instance_pre.instantiate_async(&mut store).await?;
253        Ok((instance, store))
254    }
255}
256
257/// InstanceState is the [`spin_core::Store`] `data` for an instance.
258///
259/// It is generic over the [`RuntimeFactors::InstanceState`] and any ad-hoc
260/// data needed by the caller.
261pub struct InstanceState<T, U> {
262    core: spin_core::State,
263    factors: T,
264    executor: U,
265}
266
267impl<T, U> InstanceState<T, U> {
268    /// Provides access to the [`spin_core::State`].
269    pub fn core_state(&self) -> &spin_core::State {
270        &self.core
271    }
272
273    /// Provides mutable access to the [`spin_core::State`].
274    pub fn core_state_mut(&mut self) -> &mut spin_core::State {
275        &mut self.core
276    }
277
278    /// Provides access to the [`RuntimeFactors::InstanceState`].
279    pub fn factors_instance_state(&self) -> &T {
280        &self.factors
281    }
282
283    /// Provides mutable access to the [`RuntimeFactors::InstanceState`].
284    pub fn factors_instance_state_mut(&mut self) -> &mut T {
285        &mut self.factors
286    }
287
288    /// Provides access to the ad-hoc executor instance state.
289    pub fn executor_instance_state(&self) -> &U {
290        &self.executor
291    }
292
293    /// Provides mutable access to the ad-hoc executor instance state.
294    pub fn executor_instance_state_mut(&mut self) -> &mut U {
295        &mut self.executor
296    }
297}
298
299impl<T, U> spin_core::AsState for InstanceState<T, U> {
300    fn as_state(&mut self) -> &mut spin_core::State {
301        &mut self.core
302    }
303}
304
305impl<T: RuntimeFactorsInstanceState, U> AsInstanceState<T> for InstanceState<T, U> {
306    fn as_instance_state(&mut self) -> &mut T {
307        &mut self.factors
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use spin_factor_wasi::{DummyFilesMounter, WasiFactor};
314    use spin_factors::RuntimeFactors;
315    use spin_factors_test::TestEnvironment;
316
317    use super::*;
318
319    #[derive(RuntimeFactors)]
320    struct TestFactors {
321        wasi: WasiFactor,
322    }
323
324    #[tokio::test]
325    async fn instance_builder_works() -> anyhow::Result<()> {
326        let factors = TestFactors {
327            wasi: WasiFactor::new(DummyFilesMounter),
328        };
329        let env = TestEnvironment::new(factors);
330        let locked = env.build_locked_app().await?;
331        let app = App::new("test-app", locked);
332
333        let engine_builder = spin_core::Engine::builder(&Default::default())?;
334        let executor = Arc::new(FactorsExecutor::new(engine_builder, env.factors)?);
335
336        let factors_app = executor
337            .load_app(app, Default::default(), &DummyComponentLoader)
338            .await?;
339
340        let mut instance_builder = factors_app.prepare("empty")?;
341
342        assert_eq!(instance_builder.app_component().id(), "empty");
343
344        instance_builder.store_builder().max_memory_size(1_000_000);
345
346        instance_builder
347            .factor_builder::<WasiFactor>()
348            .unwrap()
349            .args(["foo"]);
350
351        let (_instance, _store) = instance_builder.instantiate(()).await?;
352        Ok(())
353    }
354
355    struct DummyComponentLoader;
356
357    #[async_trait]
358    impl ComponentLoader<TestFactors, ()> for DummyComponentLoader {
359        async fn load_component(
360            &self,
361            engine: &spin_core::wasmtime::Engine,
362            _component: &AppComponent,
363        ) -> anyhow::Result<Component> {
364            Component::new(engine, "(component)")
365        }
366    }
367}