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
11pub 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 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 pub fn add_hooks(&mut self, hooks: impl ExecutorHooks<T, U> + 'static) {
47 self.hooks.push(Box::new(hooks));
48 }
49
50 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 async fn configure_app(&self, configured_app: &ConfiguredApp<T>) -> anyhow::Result<()> {
91 let _ = configured_app;
92 Ok(())
93 }
94
95 fn prepare_instance(&self, builder: &mut FactorsInstanceBuilder<T, U>) -> anyhow::Result<()> {
97 let _ = builder;
98 Ok(())
99 }
100}
101
102#[async_trait]
104pub trait ComponentLoader<T: RuntimeFactors, U>: Sync {
105 async fn load_component(
107 &self,
108 engine: &spin_core::wasmtime::Engine,
109 component: &AppComponent,
110 ) -> anyhow::Result<Component>;
111
112 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
126pub struct FactorsExecutorApp<T: RuntimeFactors, U> {
131 executor: Arc<FactorsExecutor<T, U>>,
132 configured_app: ConfiguredApp<T>,
133 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 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
193pub 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 pub fn app_component(&self) -> &AppComponent {
208 &self.app_component
209 }
210
211 pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
213 &mut self.store_builder
214 }
215
216 pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
218 &mut self.factor_builders
219 }
220
221 pub fn factor_builder<F: Factor>(&mut self) -> Option<&mut F::InstanceBuilder> {
223 self.factor_builders().for_factor::<F>()
224 }
225
226 pub fn wasmtime_engine(&self) -> &spin_core::WasmtimeEngine {
228 self.instance_pre.engine()
229 }
230
231 pub fn component(&self) -> &Component {
233 self.instance_pre.component()
234 }
235}
236
237impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
238 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
257pub struct InstanceState<T, U> {
262 core: spin_core::State,
263 factors: T,
264 executor: U,
265}
266
267impl<T, U> InstanceState<T, U> {
268 pub fn core_state(&self) -> &spin_core::State {
270 &self.core
271 }
272
273 pub fn core_state_mut(&mut self) -> &mut spin_core::State {
275 &mut self.core
276 }
277
278 pub fn factors_instance_state(&self) -> &T {
280 &self.factors
281 }
282
283 pub fn factors_instance_state_mut(&mut self) -> &mut T {
285 &mut self.factors
286 }
287
288 pub fn executor_instance_state(&self) -> &U {
290 &self.executor
291 }
292
293 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}