1use std::time::{Duration, Instant};
2
3use super::transaction::{
4 ArtifactBuildTransaction, ArtifactCacheError, ArtifactCacheOutcome, ArtifactCachePreparation,
5 ArtifactCacheSpec, prepare_artifact_cache,
6};
7
8#[derive(Clone, Debug, Eq, PartialEq)]
10pub struct ArtifactCacheBatchOutcome {
11 outcomes: Vec<ArtifactCacheOutcome>,
12 total: Duration,
13}
14
15#[derive(Debug)]
17pub enum ArtifactCacheBatchError<E> {
18 Cache {
20 failed_index: usize,
22 completed: Vec<ArtifactCacheOutcome>,
24 total: Duration,
26 source: Box<ArtifactCacheError>,
28 },
29 Build {
31 failed_index: usize,
33 completed: Vec<ArtifactCacheOutcome>,
35 total: Duration,
37 source: Box<E>,
39 cleanup_error: Option<Box<ArtifactCacheError>>,
41 },
42}
43
44impl ArtifactCacheBatchOutcome {
45 #[must_use]
47 pub fn outcomes(&self) -> &[ArtifactCacheOutcome] {
48 &self.outcomes
49 }
50
51 #[must_use]
53 pub fn into_outcomes(self) -> Vec<ArtifactCacheOutcome> {
54 self.outcomes
55 }
56
57 #[must_use]
59 pub const fn total(&self) -> Duration {
60 self.total
61 }
62}
63
64impl<E> ArtifactCacheBatchError<E> {
65 #[must_use]
67 pub const fn failed_index(&self) -> usize {
68 match self {
69 Self::Cache { failed_index, .. } | Self::Build { failed_index, .. } => *failed_index,
70 }
71 }
72
73 #[must_use]
75 pub fn completed(&self) -> &[ArtifactCacheOutcome] {
76 match self {
77 Self::Cache { completed, .. } | Self::Build { completed, .. } => completed,
78 }
79 }
80
81 #[must_use]
83 pub const fn total(&self) -> Duration {
84 match self {
85 Self::Cache { total, .. } | Self::Build { total, .. } => *total,
86 }
87 }
88
89 #[must_use]
91 pub fn cleanup_error(&self) -> Option<&ArtifactCacheError> {
92 match self {
93 Self::Build { cleanup_error, .. } => cleanup_error.as_deref(),
94 Self::Cache { .. } => None,
95 }
96 }
97}
98
99impl std::fmt::Display for ArtifactCacheBatchOutcome {
100 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 let reused = self
102 .outcomes
103 .iter()
104 .filter(|outcome| outcome.is_reused())
105 .count();
106 write!(
107 formatter,
108 "entries={} built={} reused={} total={:?}",
109 self.outcomes.len(),
110 self.outcomes.len().saturating_sub(reused),
111 reused,
112 self.total,
113 )
114 }
115}
116
117pub fn build_artifact_caches_batch<E, F>(
129 specs: &[ArtifactCacheSpec],
130 mut populate: F,
131) -> Result<ArtifactCacheBatchOutcome, ArtifactCacheBatchError<E>>
132where
133 F: FnMut(usize, &ArtifactBuildTransaction) -> Result<(), E>,
134{
135 let started = Instant::now();
136 let mut outcomes = Vec::with_capacity(specs.len());
137 for (index, spec) in specs.iter().enumerate() {
138 let preparation = match prepare_artifact_cache(spec) {
139 Ok(preparation) => preparation,
140 Err(source) => {
141 return Err(ArtifactCacheBatchError::Cache {
142 failed_index: index,
143 completed: outcomes,
144 total: started.elapsed(),
145 source: Box::new(source),
146 });
147 }
148 };
149 let outcome = match preparation {
150 ArtifactCachePreparation::Reused(record) => ArtifactCacheOutcome::Reused(record),
151 ArtifactCachePreparation::Build(transaction) => {
152 if let Err(source) = populate(index, &transaction) {
153 let cleanup_error = transaction.abort().err().map(Box::new);
154 return Err(ArtifactCacheBatchError::Build {
155 failed_index: index,
156 completed: outcomes,
157 total: started.elapsed(),
158 source: Box::new(source),
159 cleanup_error,
160 });
161 }
162 match transaction.commit() {
163 Ok(outcome) => outcome,
164 Err(source) => {
165 return Err(ArtifactCacheBatchError::Cache {
166 failed_index: index,
167 completed: outcomes,
168 total: started.elapsed(),
169 source: Box::new(source),
170 });
171 }
172 }
173 }
174 };
175 outcomes.push(outcome);
176 }
177 Ok(ArtifactCacheBatchOutcome {
178 outcomes,
179 total: started.elapsed(),
180 })
181}
182
183impl<E: std::fmt::Display> std::fmt::Display for ArtifactCacheBatchError<E> {
184 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 match self {
186 Self::Cache {
187 failed_index,
188 completed,
189 source,
190 ..
191 } => write!(
192 formatter,
193 "artifact cache batch entry {failed_index} failed after {} successful entry/entries: {source}",
194 completed.len(),
195 ),
196 Self::Build {
197 failed_index,
198 completed,
199 source,
200 cleanup_error,
201 ..
202 } => {
203 write!(
204 formatter,
205 "artifact cache batch builder {failed_index} failed after {} successful entry/entries: {source}",
206 completed.len(),
207 )?;
208 if let Some(cleanup_error) = cleanup_error {
209 write!(formatter, "; cleanup also failed: {cleanup_error}")?;
210 }
211 Ok(())
212 }
213 }
214 }
215}
216
217impl<E> std::error::Error for ArtifactCacheBatchError<E>
218where
219 E: std::error::Error + 'static,
220{
221 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
222 match self {
223 Self::Cache { source, .. } => Some(source.as_ref()),
224 Self::Build { source, .. } => Some(source.as_ref()),
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests;