1use std::time::{Duration, Instant};
2use std::{collections::HashMap, sync::Arc};
3
4use anyhow::Context;
5use spin_app::{App, AppComponent};
6use spin_core::{Component, async_trait, wasmtime::CallHook};
7use spin_factors::{
8 AsInstanceState, ConfiguredApp, Factor, HasInstanceBuilder, RuntimeFactors,
9 RuntimeFactorsInstanceState,
10};
11
12pub struct FactorsExecutor<T: RuntimeFactors, U: 'static = ()> {
17 core_engine: spin_core::Engine<InstanceState<T::InstanceState, U>>,
18 factors: T,
19 hooks: Vec<Box<dyn ExecutorHooks<T, U>>>,
20}
21
22impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutor<T, U> {
23 pub fn new(
25 mut core_engine_builder: spin_core::EngineBuilder<
26 InstanceState<<T as RuntimeFactors>::InstanceState, U>,
27 >,
28 mut factors: T,
29 ) -> anyhow::Result<Self> {
30 factors
31 .init(core_engine_builder.linker())
32 .context("failed to initialize factors")?;
33 Ok(Self {
34 factors,
35 core_engine: core_engine_builder.build(),
36 hooks: Default::default(),
37 })
38 }
39
40 pub fn core_engine(&self) -> &spin_core::Engine<InstanceState<T::InstanceState, U>> {
41 &self.core_engine
42 }
43
44 pub fn add_hooks(&mut self, hooks: impl ExecutorHooks<T, U> + 'static) {
48 self.hooks.push(Box::new(hooks));
49 }
50
51 pub async fn load_app(
53 self: Arc<Self>,
54 app: App,
55 runtime_config: T::RuntimeConfig,
56 component_loader: &impl ComponentLoader<T, U>,
57 trigger_type: Option<&str>,
58 trigger_dependencies_composer: impl TriggerDependenciesComposer,
59 ) -> anyhow::Result<FactorsExecutorApp<T, U>> {
60 let configured_app = self
61 .factors
62 .configure_app(app, runtime_config)
63 .context("failed to configure app")?;
64
65 for hooks in &self.hooks {
66 hooks.configure_app(&configured_app).await?;
67 }
68
69 let components = match trigger_type {
70 Some(trigger_type) => configured_app
71 .app()
72 .triggers_with_type(trigger_type)
73 .filter_map(|t| t.component().ok())
74 .collect::<Vec<_>>(),
75 None => configured_app.app().components().collect(),
76 };
77 let mut component_instance_pres = HashMap::with_capacity(components.len());
78
79 for component in components {
80 let instance_pre = component_loader
81 .load_instance_pre(
82 &self.core_engine,
83 &component,
84 &trigger_dependencies_composer,
85 )
86 .await?;
87 component_instance_pres.insert(component.id().to_string(), instance_pre);
88 }
89
90 Ok(FactorsExecutorApp {
91 executor: self.clone(),
92 configured_app,
93 component_instance_pres,
94 })
95 }
96}
97
98#[async_trait]
99pub trait ExecutorHooks<T, U>: Send + Sync
100where
101 T: RuntimeFactors,
102{
103 async fn configure_app(&self, configured_app: &ConfiguredApp<T>) -> anyhow::Result<()> {
105 let _ = configured_app;
106 Ok(())
107 }
108
109 fn prepare_instance(&self, builder: &mut FactorsInstanceBuilder<T, U>) -> anyhow::Result<()> {
111 let _ = builder;
112 Ok(())
113 }
114}
115
116#[async_trait]
118pub trait ComponentLoader<T: RuntimeFactors, U>: Sync {
119 async fn load_component(
121 &self,
122 engine: &spin_core::wasmtime::Engine,
123 component: &AppComponent,
124 trigger_dependencies_composer: &impl TriggerDependenciesComposer,
125 ) -> anyhow::Result<Component>;
126
127 async fn load_instance_pre(
129 &self,
130 engine: &spin_core::Engine<InstanceState<T::InstanceState, U>>,
131 component: &AppComponent,
132 trigger_dependencies_composer: &impl TriggerDependenciesComposer,
133 ) -> anyhow::Result<spin_core::InstancePre<InstanceState<T::InstanceState, U>>> {
134 let component = self
135 .load_component(engine.as_ref(), component, trigger_dependencies_composer)
136 .await?;
137 engine.instantiate_pre(&component)
138 }
139}
140
141#[async_trait]
142pub trait TriggerDependenciesComposer: Send + Sync {
143 async fn compose_trigger_dependencies(
144 &self,
145 trigger_dependencies: &HashMap<String, Vec<TriggerDependency>>,
146 component: Vec<u8>,
147 ) -> anyhow::Result<Vec<u8>>;
148}
149
150#[async_trait]
151impl TriggerDependenciesComposer for () {
152 async fn compose_trigger_dependencies(
153 &self,
154 trigger_dependencies: &HashMap<String, Vec<TriggerDependency>>,
155 component: Vec<u8>,
156 ) -> anyhow::Result<Vec<u8>> {
157 if trigger_dependencies.is_empty() {
158 Ok(component)
159 } else {
160 Err(anyhow::anyhow!("this trigger should not have dependencies"))
161 }
162 }
163}
164
165pub struct TriggerDependency {
166 pub data: TriggerDependencyData,
167 pub dependency: spin_app::locked::LockedComponentDependency,
168}
169
170pub enum TriggerDependencyData {
171 InMemory(Vec<u8>),
172 OnDisk(std::path::PathBuf),
173}
174
175type InstancePre<T, U> =
176 spin_core::InstancePre<InstanceState<<T as RuntimeFactors>::InstanceState, U>>;
177
178pub struct FactorsExecutorApp<T: RuntimeFactors, U: 'static> {
183 executor: Arc<FactorsExecutor<T, U>>,
184 configured_app: ConfiguredApp<T>,
185 component_instance_pres: HashMap<String, InstancePre<T, U>>,
187}
188
189impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U> {
190 pub fn engine(&self) -> &spin_core::Engine<InstanceState<T::InstanceState, U>> {
191 &self.executor.core_engine
192 }
193
194 pub fn configured_app(&self) -> &ConfiguredApp<T> {
195 &self.configured_app
196 }
197
198 pub fn app(&self) -> &App {
199 self.configured_app.app()
200 }
201
202 pub fn get_component(&self, component_id: &str) -> anyhow::Result<&Component> {
203 Ok(self.get_instance_pre(component_id)?.component())
204 }
205
206 pub fn get_instance_pre(&self, component_id: &str) -> anyhow::Result<&InstancePre<T, U>> {
207 self.component_instance_pres
208 .get(component_id)
209 .with_context(|| format!("no such component {component_id:?}"))
210 }
211
212 pub fn prepare(&self, component_id: &str) -> anyhow::Result<FactorsInstanceBuilder<'_, T, U>> {
214 let app_component = self
215 .configured_app
216 .app()
217 .get_component(component_id)
218 .with_context(|| format!("no such component {component_id:?}"))?;
219
220 let instance_pre = self.component_instance_pres.get(component_id).unwrap();
221
222 let factor_builders = self
223 .executor
224 .factors
225 .prepare(&self.configured_app, component_id)?;
226
227 let store_builder = self.executor.core_engine.store_builder();
228
229 let mut builder = FactorsInstanceBuilder {
230 store_builder,
231 factor_builders,
232 instance_pre,
233 app_component,
234 factors: &self.executor.factors,
235 };
236
237 for hooks in &self.executor.hooks {
238 hooks.prepare_instance(&mut builder)?;
239 }
240
241 Ok(builder)
242 }
243}
244
245pub struct FactorsInstanceBuilder<'a, F: RuntimeFactors, U: 'static> {
250 app_component: AppComponent<'a>,
251 store_builder: spin_core::StoreBuilder,
252 factor_builders: F::InstanceBuilders,
253 instance_pre: &'a InstancePre<F, U>,
254 factors: &'a F,
255}
256
257impl<T: RuntimeFactors, U: 'static> FactorsInstanceBuilder<'_, T, U> {
258 pub fn app_component(&self) -> &AppComponent<'_> {
260 &self.app_component
261 }
262
263 pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
265 &mut self.store_builder
266 }
267
268 pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
270 &mut self.factor_builders
271 }
272
273 pub fn factor_builder<F: Factor>(&mut self) -> Option<&mut F::InstanceBuilder> {
275 self.factor_builders().for_factor::<F>()
276 }
277
278 pub fn wasmtime_engine(&self) -> &spin_core::WasmtimeEngine {
280 self.instance_pre.engine()
281 }
282
283 pub fn component(&self) -> &Component {
285 self.instance_pre.component()
286 }
287}
288
289impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
290 pub async fn instantiate(
292 self,
293 executor_instance_state: U,
294 ) -> anyhow::Result<(
295 spin_core::Instance,
296 spin_core::Store<InstanceState<T::InstanceState, U>>,
297 )> {
298 let instance_state = InstanceState {
299 core: Default::default(),
300 factors: self.factors.build_instance_state(self.factor_builders)?,
301 executor: executor_instance_state,
302 cpu_time_elapsed: Duration::from_millis(0),
303 cpu_time_last_entry: None,
304 memory_used_on_init: 0,
305 component_id: self.app_component.id().into(),
306 };
307 let mut store = self.store_builder.build(instance_state)?;
308
309 #[cfg(feature = "cpu-time-metrics")]
310 store.as_mut().call_hook(|mut store, hook| {
311 CpuTimeCallHook.handle_call_event::<T, U>(store.data_mut(), hook)
312 });
313
314 let instance = self.instance_pre.instantiate_async(&mut store).await?;
315
316 store.data_mut().memory_used_on_init = store.data().core_state().memory_consumed();
319
320 Ok((instance, store))
321 }
322
323 pub fn instantiate_store(
324 self,
325 executor_instance_state: U,
326 ) -> anyhow::Result<spin_core::Store<InstanceState<T::InstanceState, U>>> {
327 let instance_state = InstanceState {
328 core: Default::default(),
329 factors: self.factors.build_instance_state(self.factor_builders)?,
330 executor: executor_instance_state,
331 cpu_time_elapsed: Duration::from_millis(0),
332 cpu_time_last_entry: None,
333 memory_used_on_init: 0,
334 component_id: self.app_component.id().into(),
335 };
336 self.store_builder.build(instance_state)
337 }
338}
339
340#[allow(unused)]
342struct CpuTimeCallHook;
343
344#[allow(unused)]
345impl CpuTimeCallHook {
346 fn handle_call_event<T: RuntimeFactors, U>(
347 &self,
348 state: &mut InstanceState<T::InstanceState, U>,
349 ch: CallHook,
350 ) -> wasmtime::Result<()> {
351 match ch {
352 CallHook::CallingWasm | CallHook::ReturningFromHost => {
353 debug_assert!(state.cpu_time_last_entry.is_none());
354 state.cpu_time_last_entry = Some(Instant::now());
355 }
356 CallHook::ReturningFromWasm | CallHook::CallingHost => {
357 let elapsed = state.cpu_time_last_entry.take().unwrap().elapsed();
358 state.cpu_time_elapsed += elapsed;
359 }
360 }
361
362 Ok(())
363 }
364}
365
366pub struct InstanceState<T, U> {
371 core: spin_core::State,
372 factors: T,
373 executor: U,
374 component_id: String,
376
377 cpu_time_last_entry: Option<Instant>,
379 cpu_time_elapsed: Duration,
381 memory_used_on_init: u64,
383}
384
385impl<T, U> Drop for InstanceState<T, U> {
386 fn drop(&mut self) {
387 #[cfg(feature = "cpu-time-metrics")]
389 spin_telemetry::metrics::histogram_f64!(
390 spin.component_cpu_time = self.cpu_time_elapsed.as_secs_f64(),
391 component_id = self.component_id.clone(),
392 unit = "s"
395 );
396
397 spin_telemetry::metrics::histogram_u64!(
399 spin.component_memory_used_on_init = self.memory_used_on_init,
400 component_id = self.component_id.clone(),
401 unit = "By"
402 );
403
404 spin_telemetry::metrics::histogram_u64!(
406 spin.component_memory_used = self.core.memory_consumed(),
407 component_id = self.component_id.clone(),
408 unit = "By"
409 );
410 }
411}
412
413impl<T, U> InstanceState<T, U> {
414 pub fn core_state(&self) -> &spin_core::State {
416 &self.core
417 }
418
419 pub fn core_state_mut(&mut self) -> &mut spin_core::State {
421 &mut self.core
422 }
423
424 pub fn factors_instance_state(&self) -> &T {
426 &self.factors
427 }
428
429 pub fn factors_instance_state_mut(&mut self) -> &mut T {
431 &mut self.factors
432 }
433
434 pub fn executor_instance_state(&self) -> &U {
436 &self.executor
437 }
438
439 pub fn executor_instance_state_mut(&mut self) -> &mut U {
441 &mut self.executor
442 }
443}
444
445impl<T, U> spin_core::AsState for InstanceState<T, U> {
446 fn as_state(&mut self) -> &mut spin_core::State {
447 &mut self.core
448 }
449}
450
451impl<T: RuntimeFactorsInstanceState, U> AsInstanceState<T> for InstanceState<T, U> {
452 fn as_instance_state(&mut self) -> &mut T {
453 &mut self.factors
454 }
455}
456
457#[cfg(test)]
458mod tests {
459 use spin_factor_wasi::{DummyFilesMounter, WasiFactor};
460 use spin_factors::RuntimeFactors;
461 use spin_factors_test::TestEnvironment;
462
463 use super::*;
464
465 #[derive(RuntimeFactors)]
466 struct TestFactors {
467 wasi: WasiFactor,
468 }
469
470 #[tokio::test]
471 async fn instance_builder_works() -> anyhow::Result<()> {
472 let factors = TestFactors {
473 wasi: WasiFactor::new(DummyFilesMounter),
474 };
475 let env = TestEnvironment::new(factors);
476 let locked = env.build_locked_app().await?;
477 let app = App::new("test-app", locked);
478
479 let engine_builder = spin_core::Engine::builder(&Default::default())?;
480 let executor = Arc::new(FactorsExecutor::new(engine_builder, env.factors)?);
481
482 let factors_app = executor
483 .load_app(app, Default::default(), &DummyComponentLoader, None, ())
484 .await?;
485
486 let mut instance_builder = factors_app.prepare("empty")?;
487
488 assert_eq!(instance_builder.app_component().id(), "empty");
489
490 instance_builder.store_builder().max_memory_size(1_000_000);
491
492 instance_builder
493 .factor_builder::<WasiFactor>()
494 .unwrap()
495 .args(["foo"]);
496
497 let (_instance, _store) = instance_builder.instantiate(()).await?;
498 Ok(())
499 }
500
501 struct DummyComponentLoader;
502
503 #[async_trait]
504 impl ComponentLoader<TestFactors, ()> for DummyComponentLoader {
505 async fn load_component(
506 &self,
507 engine: &spin_core::wasmtime::Engine,
508 _component: &AppComponent,
509 _trigger_dependencies_composer: &impl TriggerDependenciesComposer,
510 ) -> anyhow::Result<Component> {
511 Ok(Component::new(engine, "(component)")?)
512 }
513 }
514}