1use std::time::{Duration, Instant};
2use std::{collections::HashMap, sync::Arc};
3
4use anyhow::Context;
5use spin_app::{App, AppComponent};
6use spin_core::{async_trait, wasmtime::CallHook, Component};
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 ) -> anyhow::Result<FactorsExecutorApp<T, U>> {
59 let configured_app = self
60 .factors
61 .configure_app(app, runtime_config)
62 .context("failed to configure app")?;
63
64 for hooks in &self.hooks {
65 hooks.configure_app(&configured_app).await?;
66 }
67
68 let components = match trigger_type {
69 Some(trigger_type) => configured_app
70 .app()
71 .triggers_with_type(trigger_type)
72 .filter_map(|t| t.component().ok())
73 .collect::<Vec<_>>(),
74 None => configured_app.app().components().collect(),
75 };
76 let mut component_instance_pres = HashMap::with_capacity(components.len());
77
78 for component in components {
79 let instance_pre = component_loader
80 .load_instance_pre(&self.core_engine, &component)
81 .await?;
82 component_instance_pres.insert(component.id().to_string(), instance_pre);
83 }
84
85 Ok(FactorsExecutorApp {
86 executor: self.clone(),
87 configured_app,
88 component_instance_pres,
89 })
90 }
91}
92
93#[async_trait]
94pub trait ExecutorHooks<T, U>: Send + Sync
95where
96 T: RuntimeFactors,
97{
98 async fn configure_app(&self, configured_app: &ConfiguredApp<T>) -> anyhow::Result<()> {
100 let _ = configured_app;
101 Ok(())
102 }
103
104 fn prepare_instance(&self, builder: &mut FactorsInstanceBuilder<T, U>) -> anyhow::Result<()> {
106 let _ = builder;
107 Ok(())
108 }
109}
110
111#[async_trait]
113pub trait ComponentLoader<T: RuntimeFactors, U>: Sync {
114 async fn load_component(
116 &self,
117 engine: &spin_core::wasmtime::Engine,
118 component: &AppComponent,
119 ) -> anyhow::Result<Component>;
120
121 async fn load_instance_pre(
123 &self,
124 engine: &spin_core::Engine<InstanceState<T::InstanceState, U>>,
125 component: &AppComponent,
126 ) -> anyhow::Result<spin_core::InstancePre<InstanceState<T::InstanceState, U>>> {
127 let component = self.load_component(engine.as_ref(), component).await?;
128 engine.instantiate_pre(&component)
129 }
130}
131
132type InstancePre<T, U> =
133 spin_core::InstancePre<InstanceState<<T as RuntimeFactors>::InstanceState, U>>;
134
135pub struct FactorsExecutorApp<T: RuntimeFactors, U: 'static> {
140 executor: Arc<FactorsExecutor<T, U>>,
141 configured_app: ConfiguredApp<T>,
142 component_instance_pres: HashMap<String, InstancePre<T, U>>,
144}
145
146impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U> {
147 pub fn engine(&self) -> &spin_core::Engine<InstanceState<T::InstanceState, U>> {
148 &self.executor.core_engine
149 }
150
151 pub fn configured_app(&self) -> &ConfiguredApp<T> {
152 &self.configured_app
153 }
154
155 pub fn app(&self) -> &App {
156 self.configured_app.app()
157 }
158
159 pub fn get_component(&self, component_id: &str) -> anyhow::Result<&Component> {
160 Ok(self.get_instance_pre(component_id)?.component())
161 }
162
163 pub fn get_instance_pre(&self, component_id: &str) -> anyhow::Result<&InstancePre<T, U>> {
164 self.component_instance_pres
165 .get(component_id)
166 .with_context(|| format!("no such component {component_id:?}"))
167 }
168
169 pub fn prepare(&self, component_id: &str) -> anyhow::Result<FactorsInstanceBuilder<'_, T, U>> {
171 let app_component = self
172 .configured_app
173 .app()
174 .get_component(component_id)
175 .with_context(|| format!("no such component {component_id:?}"))?;
176
177 let instance_pre = self.component_instance_pres.get(component_id).unwrap();
178
179 let factor_builders = self
180 .executor
181 .factors
182 .prepare(&self.configured_app, component_id)?;
183
184 let store_builder = self.executor.core_engine.store_builder();
185
186 let mut builder = FactorsInstanceBuilder {
187 store_builder,
188 factor_builders,
189 instance_pre,
190 app_component,
191 factors: &self.executor.factors,
192 };
193
194 for hooks in &self.executor.hooks {
195 hooks.prepare_instance(&mut builder)?;
196 }
197
198 Ok(builder)
199 }
200}
201
202pub struct FactorsInstanceBuilder<'a, F: RuntimeFactors, U: 'static> {
207 app_component: AppComponent<'a>,
208 store_builder: spin_core::StoreBuilder,
209 factor_builders: F::InstanceBuilders,
210 instance_pre: &'a InstancePre<F, U>,
211 factors: &'a F,
212}
213
214impl<T: RuntimeFactors, U: 'static> FactorsInstanceBuilder<'_, T, U> {
215 pub fn app_component(&self) -> &AppComponent<'_> {
217 &self.app_component
218 }
219
220 pub fn store_builder(&mut self) -> &mut spin_core::StoreBuilder {
222 &mut self.store_builder
223 }
224
225 pub fn factor_builders(&mut self) -> &mut T::InstanceBuilders {
227 &mut self.factor_builders
228 }
229
230 pub fn factor_builder<F: Factor>(&mut self) -> Option<&mut F::InstanceBuilder> {
232 self.factor_builders().for_factor::<F>()
233 }
234
235 pub fn wasmtime_engine(&self) -> &spin_core::WasmtimeEngine {
237 self.instance_pre.engine()
238 }
239
240 pub fn component(&self) -> &Component {
242 self.instance_pre.component()
243 }
244}
245
246impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
247 pub async fn instantiate(
249 self,
250 executor_instance_state: U,
251 ) -> anyhow::Result<(
252 spin_core::Instance,
253 spin_core::Store<InstanceState<T::InstanceState, U>>,
254 )> {
255 let instance_state = InstanceState {
256 core: Default::default(),
257 factors: self.factors.build_instance_state(self.factor_builders)?,
258 executor: executor_instance_state,
259 cpu_time_elapsed: Duration::from_millis(0),
260 cpu_time_last_entry: None,
261 memory_used_on_init: 0,
262 component_id: self.app_component.id().into(),
263 };
264 let mut store = self.store_builder.build(instance_state)?;
265
266 #[cfg(feature = "cpu-time-metrics")]
267 store.as_mut().call_hook(|mut store, hook| {
268 CpuTimeCallHook.handle_call_event::<T, U>(store.data_mut(), hook)
269 });
270
271 let instance = self.instance_pre.instantiate_async(&mut store).await?;
272
273 store.data_mut().memory_used_on_init = store.data().core_state().memory_consumed();
276
277 Ok((instance, store))
278 }
279
280 pub fn instantiate_store(
281 self,
282 executor_instance_state: U,
283 ) -> anyhow::Result<spin_core::Store<InstanceState<T::InstanceState, U>>> {
284 let instance_state = InstanceState {
285 core: Default::default(),
286 factors: self.factors.build_instance_state(self.factor_builders)?,
287 executor: executor_instance_state,
288 cpu_time_elapsed: Duration::from_millis(0),
289 cpu_time_last_entry: None,
290 memory_used_on_init: 0,
291 component_id: self.app_component.id().into(),
292 };
293 self.store_builder.build(instance_state)
294 }
295}
296
297#[allow(unused)]
299struct CpuTimeCallHook;
300
301#[allow(unused)]
302impl CpuTimeCallHook {
303 fn handle_call_event<T: RuntimeFactors, U>(
304 &self,
305 state: &mut InstanceState<T::InstanceState, U>,
306 ch: CallHook,
307 ) -> wasmtime::Result<()> {
308 match ch {
309 CallHook::CallingWasm | CallHook::ReturningFromHost => {
310 debug_assert!(state.cpu_time_last_entry.is_none());
311 state.cpu_time_last_entry = Some(Instant::now());
312 }
313 CallHook::ReturningFromWasm | CallHook::CallingHost => {
314 let elapsed = state.cpu_time_last_entry.take().unwrap().elapsed();
315 state.cpu_time_elapsed += elapsed;
316 }
317 }
318
319 Ok(())
320 }
321}
322
323pub struct InstanceState<T, U> {
328 core: spin_core::State,
329 factors: T,
330 executor: U,
331 component_id: String,
333
334 cpu_time_last_entry: Option<Instant>,
336 cpu_time_elapsed: Duration,
338 memory_used_on_init: u64,
340}
341
342impl<T, U> Drop for InstanceState<T, U> {
343 fn drop(&mut self) {
344 #[cfg(feature = "cpu-time-metrics")]
346 spin_telemetry::metrics::histogram!(
347 spin.component_cpu_time = self.cpu_time_elapsed.as_secs_f64(),
348 component_id = self.component_id,
349 unit = "s"
352 );
353
354 spin_telemetry::metrics::histogram!(
356 spin.component_memory_used_on_init = self.memory_used_on_init,
357 component_id = self.component_id,
358 unit = "By"
359 );
360
361 spin_telemetry::metrics::histogram!(
363 spin.component_memory_used = self.core.memory_consumed(),
364 component_id = self.component_id,
365 unit = "By"
366 );
367 }
368}
369
370impl<T, U> InstanceState<T, U> {
371 pub fn core_state(&self) -> &spin_core::State {
373 &self.core
374 }
375
376 pub fn core_state_mut(&mut self) -> &mut spin_core::State {
378 &mut self.core
379 }
380
381 pub fn factors_instance_state(&self) -> &T {
383 &self.factors
384 }
385
386 pub fn factors_instance_state_mut(&mut self) -> &mut T {
388 &mut self.factors
389 }
390
391 pub fn executor_instance_state(&self) -> &U {
393 &self.executor
394 }
395
396 pub fn executor_instance_state_mut(&mut self) -> &mut U {
398 &mut self.executor
399 }
400}
401
402impl<T, U> spin_core::AsState for InstanceState<T, U> {
403 fn as_state(&mut self) -> &mut spin_core::State {
404 &mut self.core
405 }
406}
407
408impl<T: RuntimeFactorsInstanceState, U> AsInstanceState<T> for InstanceState<T, U> {
409 fn as_instance_state(&mut self) -> &mut T {
410 &mut self.factors
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use spin_factor_wasi::{DummyFilesMounter, WasiFactor};
417 use spin_factors::RuntimeFactors;
418 use spin_factors_test::TestEnvironment;
419
420 use super::*;
421
422 #[derive(RuntimeFactors)]
423 struct TestFactors {
424 wasi: WasiFactor,
425 }
426
427 #[tokio::test]
428 async fn instance_builder_works() -> anyhow::Result<()> {
429 let factors = TestFactors {
430 wasi: WasiFactor::new(DummyFilesMounter),
431 };
432 let env = TestEnvironment::new(factors);
433 let locked = env.build_locked_app().await?;
434 let app = App::new("test-app", locked);
435
436 let engine_builder = spin_core::Engine::builder(&Default::default())?;
437 let executor = Arc::new(FactorsExecutor::new(engine_builder, env.factors)?);
438
439 let factors_app = executor
440 .load_app(app, Default::default(), &DummyComponentLoader, None)
441 .await?;
442
443 let mut instance_builder = factors_app.prepare("empty")?;
444
445 assert_eq!(instance_builder.app_component().id(), "empty");
446
447 instance_builder.store_builder().max_memory_size(1_000_000);
448
449 instance_builder
450 .factor_builder::<WasiFactor>()
451 .unwrap()
452 .args(["foo"]);
453
454 let (_instance, _store) = instance_builder.instantiate(()).await?;
455 Ok(())
456 }
457
458 struct DummyComponentLoader;
459
460 #[async_trait]
461 impl ComponentLoader<TestFactors, ()> for DummyComponentLoader {
462 async fn load_component(
463 &self,
464 engine: &spin_core::wasmtime::Engine,
465 _component: &AppComponent,
466 ) -> anyhow::Result<Component> {
467 Ok(Component::new(engine, "(component)")?)
468 }
469 }
470}