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 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 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
191pub 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 pub fn app_component(&self) -> &AppComponent {
206 &self.app_component
207 }
208
209 pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
211 &mut self.store_builder
212 }
213
214 pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
216 &mut self.factor_builders
217 }
218
219 pub fn factor_builder<F: Factor>(&mut self) -> Option<&mut F::InstanceBuilder> {
221 self.factor_builders().for_factor::<F>()
222 }
223
224 pub fn wasmtime_engine(&self) -> &spin_core::WasmtimeEngine {
226 self.instance_pre.engine()
227 }
228
229 pub fn component(&self) -> &Component {
231 self.instance_pre.component()
232 }
233}
234
235impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
236 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
255pub struct InstanceState<T, U> {
260 core: spin_core::State,
261 factors: T,
262 executor: U,
263}
264
265impl<T, U> InstanceState<T, U> {
266 pub fn core_state(&self) -> &spin_core::State {
268 &self.core
269 }
270
271 pub fn core_state_mut(&mut self) -> &mut spin_core::State {
273 &mut self.core
274 }
275
276 pub fn factors_instance_state(&self) -> &T {
278 &self.factors
279 }
280
281 pub fn factors_instance_state_mut(&mut self) -> &mut T {
283 &mut self.factors
284 }
285
286 pub fn executor_instance_state(&self) -> &U {
288 &self.executor
289 }
290
291 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}