Skip to content

Commit 30aa1fc

Browse files
committed
Load and compose components once at start of validation
Signed-off-by: itowlson <[email protected]>
1 parent 2900e6e commit 30aa1fc

File tree

2 files changed

+80
-49
lines changed

2 files changed

+80
-49
lines changed

crates/environments/src/lib.rs

+25-27
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ mod loader;
55

66
use environment_definition::{TargetEnvironment, TargetWorld, TriggerType};
77
pub use loader::ResolutionContext;
8-
use loader::{component_source, ComponentSourceLoader, ComponentToValidate};
8+
use loader::{load_and_resolve_all, ComponentToValidate};
99

1010
pub async fn validate_application_against_environment_ids(
1111
env_ids: impl Iterator<Item = &str>,
1212
app: &spin_manifest::schema::v2::AppManifest,
1313
resolution_context: &ResolutionContext,
1414
) -> anyhow::Result<()> {
15-
let envs = futures::future::join_all(env_ids.map(resolve_environment_id)).await;
16-
let envs: Vec<_> = envs.into_iter().collect::<Result<_, _>>()?;
15+
let envs = join_all_result(env_ids.map(resolve_environment_id)).await?;
1716
validate_application_against_environments(&envs, app, resolution_context).await
1817
}
1918

@@ -54,6 +53,8 @@ pub async fn validate_application_against_environments(
5453
app: &spin_manifest::schema::v2::AppManifest,
5554
resolution_context: &ResolutionContext,
5655
) -> anyhow::Result<()> {
56+
use futures::FutureExt;
57+
5758
for trigger_type in app.triggers.keys() {
5859
if let Some(env) = envs
5960
.iter()
@@ -66,26 +67,15 @@ pub async fn validate_application_against_environments(
6667
}
6768
}
6869

69-
let components_by_trigger_type = app
70-
.triggers
71-
.iter()
72-
.map(|(ty, ts)| {
73-
ts.iter()
74-
.map(|t| component_source(app, t))
75-
.collect::<Result<Vec<_>, _>>()
76-
.map(|css| (ty, css))
77-
})
78-
.collect::<Result<Vec<_>, _>>()?;
70+
let components_by_trigger_type_futs = app.triggers.iter().map(|(ty, ts)| {
71+
load_and_resolve_all(app, ts, resolution_context)
72+
.map(|css| css.map(|css| (ty.to_owned(), css)))
73+
});
74+
let components_by_trigger_type = join_all_result(components_by_trigger_type_futs).await?;
7975

8076
for (trigger_type, component) in components_by_trigger_type {
8177
for component in &component {
82-
validate_component_against_environments(
83-
envs,
84-
trigger_type,
85-
component,
86-
resolution_context,
87-
)
88-
.await?;
78+
validate_component_against_environments(envs, &trigger_type, component).await?;
8979
}
9080
}
9181

@@ -96,7 +86,6 @@ async fn validate_component_against_environments(
9686
envs: &[TargetEnvironment],
9787
trigger_type: &TriggerType,
9888
component: &ComponentToValidate<'_>,
99-
resolution_context: &ResolutionContext,
10089
) -> anyhow::Result<()> {
10190
let worlds = envs
10291
.iter()
@@ -110,21 +99,18 @@ async fn validate_component_against_environments(
11099
.map(|w| (e.name.as_str(), w))
111100
})
112101
.collect::<Result<std::collections::HashSet<_>, _>>()?;
113-
validate_component_against_worlds(worlds.into_iter(), component, resolution_context).await?;
102+
validate_component_against_worlds(worlds.into_iter(), component).await?;
114103
Ok(())
115104
}
116105

117106
async fn validate_component_against_worlds(
118107
target_worlds: impl Iterator<Item = (&str, &TargetWorld)>,
119108
component: &ComponentToValidate<'_>,
120-
resolution_context: &ResolutionContext,
121109
) -> anyhow::Result<()> {
122-
let loader = ComponentSourceLoader::new(resolution_context.wasm_loader());
123-
let wasm_bytes = spin_compose::compose(&loader, component).await?;
110+
let wasm_bytes = component.wasm_bytes();
124111

125112
for (env_name, target_world) in target_worlds {
126-
validate_wasm_against_any_world(env_name, target_world, component, wasm_bytes.as_ref())
127-
.await?;
113+
validate_wasm_against_any_world(env_name, target_world, component, wasm_bytes).await?;
128114
}
129115

130116
tracing::info!(
@@ -223,3 +209,15 @@ async fn validate_wasm_against_world(
223209
},
224210
}
225211
}
212+
213+
/// Equivalent to futures::future::join_all, but specialised for iterators of
214+
/// fallible futures. It returns a Result<Vec<...>> instead of a Vec<Result<...>> -
215+
/// this just moves the transposition boilerplate out of the main flow.
216+
async fn join_all_result<T, I>(iter: I) -> anyhow::Result<Vec<T>>
217+
where
218+
I: IntoIterator,
219+
I::Item: std::future::Future<Output = anyhow::Result<T>>,
220+
{
221+
let vec_result = futures::future::join_all(iter).await;
222+
vec_result.into_iter().collect()
223+
}

crates/environments/src/loader.rs

+55-22
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ use anyhow::anyhow;
44
use spin_common::ui::quoted_path;
55

66
pub(crate) struct ComponentToValidate<'a> {
7+
id: &'a str,
8+
source_description: String,
9+
wasm: Vec<u8>,
10+
}
11+
12+
struct ComponentSource<'a> {
713
id: &'a str,
814
source: &'a spin_manifest::schema::v2::ComponentSource,
915
dependencies: WrappedComponentDependencies,
@@ -14,22 +20,30 @@ impl<'a> ComponentToValidate<'a> {
1420
self.id
1521
}
1622

17-
pub fn source_description(&self) -> String {
18-
match self.source {
19-
spin_manifest::schema::v2::ComponentSource::Local(path) => {
20-
format!("file {}", quoted_path(path))
21-
}
22-
spin_manifest::schema::v2::ComponentSource::Remote { url, .. } => format!("URL {url}"),
23-
spin_manifest::schema::v2::ComponentSource::Registry { package, .. } => {
24-
format!("package {package}")
25-
}
26-
}
23+
pub fn source_description(&self) -> &str {
24+
&self.source_description
25+
}
26+
27+
pub fn wasm_bytes(&self) -> &[u8] {
28+
&self.wasm
2729
}
2830
}
2931

30-
pub fn component_source<'a>(
32+
pub async fn load_and_resolve_all<'a>(
33+
app: &'a spin_manifest::schema::v2::AppManifest,
34+
triggers: &'a [spin_manifest::schema::v2::Trigger],
35+
resolution_context: &'a ResolutionContext,
36+
) -> anyhow::Result<Vec<ComponentToValidate<'a>>> {
37+
let component_futures = triggers
38+
.iter()
39+
.map(|t| load_and_resolve_one(app, t, resolution_context));
40+
crate::join_all_result(component_futures).await
41+
}
42+
43+
async fn load_and_resolve_one<'a>(
3144
app: &'a spin_manifest::schema::v2::AppManifest,
3245
trigger: &'a spin_manifest::schema::v2::Trigger,
46+
resolution_context: &'a ResolutionContext,
3347
) -> anyhow::Result<ComponentToValidate<'a>> {
3448
let component_spec = trigger
3549
.component
@@ -50,10 +64,21 @@ pub fn component_source<'a>(
5064
(id, &component.source, &component.dependencies)
5165
}
5266
};
53-
Ok(ComponentToValidate {
67+
68+
let component = ComponentSource {
5469
id,
5570
source,
5671
dependencies: WrappedComponentDependencies::new(dependencies),
72+
};
73+
74+
let loader = ComponentSourceLoader::new(resolution_context.wasm_loader());
75+
76+
let wasm = spin_compose::compose(&loader, &component).await?;
77+
78+
Ok(ComponentToValidate {
79+
id,
80+
source_description: source_description(component.source),
81+
wasm,
5782
})
5883
}
5984

@@ -68,33 +93,29 @@ impl ResolutionContext {
6893
Ok(Self { wasm_loader })
6994
}
7095

71-
pub(crate) fn wasm_loader(&self) -> &spin_loader::WasmLoader {
96+
fn wasm_loader(&self) -> &spin_loader::WasmLoader {
7297
&self.wasm_loader
7398
}
7499
}
75100

76-
pub(crate) struct ComponentSourceLoader<'a> {
101+
struct ComponentSourceLoader<'a> {
77102
wasm_loader: &'a spin_loader::WasmLoader,
78-
_phantom: std::marker::PhantomData<&'a usize>,
79103
}
80104

81105
impl<'a> ComponentSourceLoader<'a> {
82106
pub fn new(wasm_loader: &'a spin_loader::WasmLoader) -> Self {
83-
Self {
84-
wasm_loader,
85-
_phantom: std::marker::PhantomData,
86-
}
107+
Self { wasm_loader }
87108
}
88109
}
89110

90111
#[async_trait::async_trait]
91112
impl<'a> spin_compose::ComponentSourceLoader for ComponentSourceLoader<'a> {
92-
type Component = ComponentToValidate<'a>;
113+
type Component = ComponentSource<'a>;
93114
type Dependency = WrappedComponentDependency;
94115
async fn load_component_source(&self, source: &Self::Component) -> anyhow::Result<Vec<u8>> {
95116
let path = self
96117
.wasm_loader
97-
.load_component_source(source.id(), source.source)
118+
.load_component_source(source.id, source.source)
98119
.await?;
99120
let bytes = tokio::fs::read(&path).await?;
100121
let component = spin_componentize::componentize_if_necessary(&bytes)?;
@@ -144,7 +165,7 @@ impl WrappedComponentDependencies {
144165
}
145166

146167
#[async_trait::async_trait]
147-
impl<'a> spin_compose::ComponentLike for ComponentToValidate<'a> {
168+
impl<'a> spin_compose::ComponentLike for ComponentSource<'a> {
148169
type Dependency = WrappedComponentDependency;
149170

150171
fn dependencies(
@@ -176,3 +197,15 @@ impl spin_compose::DependencyLike for WrappedComponentDependency {
176197
}
177198
}
178199
}
200+
201+
fn source_description(source: &spin_manifest::schema::v2::ComponentSource) -> String {
202+
match source {
203+
spin_manifest::schema::v2::ComponentSource::Local(path) => {
204+
format!("file {}", quoted_path(path))
205+
}
206+
spin_manifest::schema::v2::ComponentSource::Remote { url, .. } => format!("URL {url}"),
207+
spin_manifest::schema::v2::ComponentSource::Registry { package, .. } => {
208+
format!("package {package}")
209+
}
210+
}
211+
}

0 commit comments

Comments
 (0)