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: 'static = ()> {
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 trigger_type: Option<&str>,
57 ) -> anyhow::Result<FactorsExecutorApp<T, U>> {
58 let configured_app = self
59 .factors
60 .configure_app(app, runtime_config)
61 .context("failed to configure app")?;
62
63 for hooks in &self.hooks {
64 hooks.configure_app(&configured_app).await?;
65 }
66
67 let components = match trigger_type {
68 Some(trigger_type) => configured_app
69 .app()
70 .triggers_with_type(trigger_type)
71 .filter_map(|t| t.component().ok())
72 .collect::<Vec<_>>(),
73 None => configured_app.app().components().collect(),
74 };
75 let mut component_instance_pres = HashMap::with_capacity(components.len());
76
77 for component in components {
78 let instance_pre = component_loader
79 .load_instance_pre(&self.core_engine, &component)
80 .await?;
81 component_instance_pres.insert(component.id().to_string(), instance_pre);
82 }
83
84 Ok(FactorsExecutorApp {
85 executor: self.clone(),
86 configured_app,
87 component_instance_pres,
88 })
89 }
90}
91
92#[async_trait]
93pub trait ExecutorHooks<T, U>: Send + Sync
94where
95 T: RuntimeFactors,
96{
97 async fn configure_app(&self, configured_app: &ConfiguredApp<T>) -> anyhow::Result<()> {
99 let _ = configured_app;
100 Ok(())
101 }
102
103 fn prepare_instance(&self, builder: &mut FactorsInstanceBuilder<T, U>) -> anyhow::Result<()> {
105 let _ = builder;
106 Ok(())
107 }
108}
109
110#[async_trait]
112pub trait ComponentLoader<T: RuntimeFactors, U>: Sync {
113 async fn load_component(
115 &self,
116 engine: &spin_core::wasmtime::Engine,
117 component: &AppComponent,
118 ) -> anyhow::Result<Component>;
119
120 async fn load_instance_pre(
122 &self,
123 engine: &spin_core::Engine<InstanceState<T::InstanceState, U>>,
124 component: &AppComponent,
125 ) -> anyhow::Result<spin_core::InstancePre<InstanceState<T::InstanceState, U>>> {
126 let component = self.load_component(engine.as_ref(), component).await?;
127 engine.instantiate_pre(&component)
128 }
129}
130
131type InstancePre<T, U> =
132 spin_core::InstancePre<InstanceState<<T as RuntimeFactors>::InstanceState, U>>;
133
134pub struct FactorsExecutorApp<T: RuntimeFactors, U: 'static> {
139 executor: Arc<FactorsExecutor<T, U>>,
140 configured_app: ConfiguredApp<T>,
141 component_instance_pres: HashMap<String, InstancePre<T, U>>,
143}
144
145impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U> {
146 pub fn engine(&self) -> &spin_core::Engine<InstanceState<T::InstanceState, U>> {
147 &self.executor.core_engine
148 }
149
150 pub fn configured_app(&self) -> &ConfiguredApp<T> {
151 &self.configured_app
152 }
153
154 pub fn app(&self) -> &App {
155 self.configured_app.app()
156 }
157
158 pub fn get_component(&self, component_id: &str) -> anyhow::Result<&Component> {
159 Ok(self.get_instance_pre(component_id)?.component())
160 }
161
162 pub fn get_instance_pre(&self, component_id: &str) -> anyhow::Result<&InstancePre<T, U>> {
163 self.component_instance_pres
164 .get(component_id)
165 .with_context(|| format!("no such component {component_id:?}"))
166 }
167
168 pub fn prepare(&self, component_id: &str) -> anyhow::Result<FactorsInstanceBuilder<'_, T, U>> {
170 let app_component = self
171 .configured_app
172 .app()
173 .get_component(component_id)
174 .with_context(|| format!("no such component {component_id:?}"))?;
175
176 let instance_pre = self.component_instance_pres.get(component_id).unwrap();
177
178 let factor_builders = self
179 .executor
180 .factors
181 .prepare(&self.configured_app, component_id)?;
182
183 let store_builder = self.executor.core_engine.store_builder();
184
185 let mut builder = FactorsInstanceBuilder {
186 store_builder,
187 factor_builders,
188 instance_pre,
189 app_component,
190 factors: &self.executor.factors,
191 };
192
193 for hooks in &self.executor.hooks {
194 hooks.prepare_instance(&mut builder)?;
195 }
196
197 Ok(builder)
198 }
199}
200
201pub struct FactorsInstanceBuilder<'a, F: RuntimeFactors, U: 'static> {
206 app_component: AppComponent<'a>,
207 store_builder: spin_core::StoreBuilder,
208 factor_builders: F::InstanceBuilders,
209 instance_pre: &'a InstancePre<F, U>,
210 factors: &'a F,
211}
212
213impl<T: RuntimeFactors, U: 'static> FactorsInstanceBuilder<'_, T, U> {
214 pub fn app_component(&self) -> &AppComponent<'_> {
216 &self.app_component
217 }
218
219 pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
221 &mut self.store_builder
222 }
223
224 pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
226 &mut self.factor_builders
227 }
228
229 pub fn factor_builder<F: Factor>(&mut self) -> Option<&mut F::InstanceBuilder> {
231 self.factor_builders().for_factor::<F>()
232 }
233
234 pub fn wasmtime_engine(&self) -> &spin_core::WasmtimeEngine {
236 self.instance_pre.engine()
237 }
238
239 pub fn component(&self) -> &Component {
241 self.instance_pre.component()
242 }
243}
244
245impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
246 pub async fn instantiate(
248 self,
249 executor_instance_state: U,
250 ) -> anyhow::Result<(
251 spin_core::Instance,
252 spin_core::Store<InstanceState<T::InstanceState, U>>,
253 )> {
254 let instance_state = InstanceState {
255 core: Default::default(),
256 factors: self.factors.build_instance_state(self.factor_builders)?,
257 executor: executor_instance_state,
258 };
259 let mut store = self.store_builder.build(instance_state)?;
260 let instance = self.instance_pre.instantiate_async(&mut store).await?;
261 Ok((instance, store))
262 }
263}
264
265pub struct InstanceState<T, U> {
270 core: spin_core::State,
271 factors: T,
272 executor: U,
273}
274
275impl<T, U> InstanceState<T, U> {
276 pub fn core_state(&self) -> &spin_core::State {
278 &self.core
279 }
280
281 pub fn core_state_mut(&mut self) -> &mut spin_core::State {
283 &mut self.core
284 }
285
286 pub fn factors_instance_state(&self) -> &T {
288 &self.factors
289 }
290
291 pub fn factors_instance_state_mut(&mut self) -> &mut T {
293 &mut self.factors
294 }
295
296 pub fn executor_instance_state(&self) -> &U {
298 &self.executor
299 }
300
301 pub fn executor_instance_state_mut(&mut self) -> &mut U {
303 &mut self.executor
304 }
305}
306
307impl<T, U> spin_core::AsState for InstanceState<T, U> {
308 fn as_state(&mut self) -> &mut spin_core::State {
309 &mut self.core
310 }
311}
312
313impl<T: RuntimeFactorsInstanceState, U> AsInstanceState<T> for InstanceState<T, U> {
314 fn as_instance_state(&mut self) -> &mut T {
315 &mut self.factors
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use spin_factor_wasi::{DummyFilesMounter, WasiFactor};
322 use spin_factors::RuntimeFactors;
323 use spin_factors_test::TestEnvironment;
324
325 use super::*;
326
327 #[derive(RuntimeFactors)]
328 struct TestFactors {
329 wasi: WasiFactor,
330 }
331
332 #[tokio::test]
333 async fn instance_builder_works() -> anyhow::Result<()> {
334 let factors = TestFactors {
335 wasi: WasiFactor::new(DummyFilesMounter),
336 };
337 let env = TestEnvironment::new(factors);
338 let locked = env.build_locked_app().await?;
339 let app = App::new("test-app", locked);
340
341 let engine_builder = spin_core::Engine::builder(&Default::default())?;
342 let executor = Arc::new(FactorsExecutor::new(engine_builder, env.factors)?);
343
344 let factors_app = executor
345 .load_app(app, Default::default(), &DummyComponentLoader, None)
346 .await?;
347
348 let mut instance_builder = factors_app.prepare("empty")?;
349
350 assert_eq!(instance_builder.app_component().id(), "empty");
351
352 instance_builder.store_builder().max_memory_size(1_000_000);
353
354 instance_builder
355 .factor_builder::<WasiFactor>()
356 .unwrap()
357 .args(["foo"]);
358
359 let (_instance, _store) = instance_builder.instantiate(()).await?;
360 Ok(())
361 }
362
363 struct DummyComponentLoader;
364
365 #[async_trait]
366 impl ComponentLoader<TestFactors, ()> for DummyComponentLoader {
367 async fn load_component(
368 &self,
369 engine: &spin_core::wasmtime::Engine,
370 _component: &AppComponent,
371 ) -> anyhow::Result<Component> {
372 Component::new(engine, "(component)")
373 }
374 }
375}