Skip to content

Commit 3d2ba56

Browse files
ovmf-prebuilt: Add download retry
If the download fails, retry up to four times. The first retry will happen after a one-second delay. Each subsequent retry doubles the delay.
1 parent 4d0f89c commit 3d2ba56

File tree

1 file changed

+81
-2
lines changed

1 file changed

+81
-2
lines changed

ovmf-prebuilt/src/fetch.rs

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
use crate::{Error, Source};
22
use log::info;
33
use sha2::{Digest, Sha256};
4-
use std::fs;
54
use std::io::{self, Cursor, Read};
65
use std::path::{Path, PathBuf};
6+
use std::time::Duration;
7+
use std::{fs, thread};
78
use tar::Archive;
89
use ureq::Agent;
910

@@ -13,6 +14,9 @@ const USER_AGENT: &str = "https://github.com/rust-osdev/ovmf-prebuilt";
1314
/// Maximum number of bytes to download (10 MiB).
1415
const MAX_DOWNLOAD_SIZE_IN_BYTES: usize = 10 * 1024 * 1024;
1516

17+
/// Maximum number of times to retry the download if it fails.
18+
const MAX_DOWNLOAD_RETRIES: usize = 4;
19+
1620
/// Update the local cache. Does nothing if the cache is already up to date.
1721
pub(crate) fn update_cache(source: Source, prebuilt_dir: &Path) -> Result<(), Error> {
1822
let hash_path = prebuilt_dir.join("sha256");
@@ -31,7 +35,7 @@ pub(crate) fn update_cache(source: Source, prebuilt_dir: &Path) -> Result<(), Er
3135
release = source.tag
3236
);
3337

34-
let data = download_url(&url)?;
38+
let data = retry(MAX_DOWNLOAD_RETRIES, || download_url(&url))?;
3539

3640
// Validate the hash.
3741
let actual_hash = format!("{:x}", Sha256::digest(&data));
@@ -82,6 +86,39 @@ fn download_url(url: &str) -> Result<Vec<u8>, Error> {
8286
Ok(data)
8387
}
8488

89+
/// Attempt an operation and retry on failure.
90+
///
91+
/// Call `f`, and retry up to `max_retries` times if it fails. If the
92+
/// final attempt fails, the error result of `f` will be returned.
93+
///
94+
/// The first retry will happen after a one-second delay. Each
95+
/// subsequent retry doubles the delay.
96+
fn retry<F>(max_retries: usize, mut f: F) -> Result<Vec<u8>, Error>
97+
where
98+
F: FnMut() -> Result<Vec<u8>, Error>,
99+
{
100+
let mut delay = Duration::from_secs(1);
101+
let max_attempts = 1 + max_retries;
102+
for attempt in 1..=max_attempts {
103+
match f() {
104+
Ok(r) => return Ok(r),
105+
Err(err) => {
106+
// Return the error if this is the last iteration.
107+
if attempt == max_attempts {
108+
return Err(err);
109+
}
110+
111+
// Otherwise, sleep and double the delay time.
112+
info!("sleeping for {delay:?} before retrying...");
113+
thread::sleep(delay);
114+
delay *= 2;
115+
}
116+
}
117+
}
118+
119+
unreachable!();
120+
}
121+
85122
fn decompress(data: &[u8]) -> Result<Vec<u8>, Error> {
86123
info!("decompressing tarball");
87124
let mut decompressed = Vec::new();
@@ -120,3 +157,45 @@ fn extract(tarball_data: &[u8], prebuilt_dir: &Path) -> Result<(), io::Error> {
120157

121158
Ok(())
122159
}
160+
161+
#[cfg(test)]
162+
mod tests {
163+
use super::*;
164+
165+
#[test]
166+
fn test_retry_immediate_success() {
167+
let mut attempts = 0;
168+
retry(4, || {
169+
attempts += 1;
170+
Ok(vec![])
171+
})
172+
.unwrap();
173+
assert_eq!(attempts, 1);
174+
}
175+
176+
#[test]
177+
fn test_retry_one_retry() {
178+
let mut attempts = 0;
179+
retry(4, || {
180+
attempts += 1;
181+
if attempts < 2 {
182+
Err(Error::Download(io::ErrorKind::Interrupted.into()))
183+
} else {
184+
Ok(vec![])
185+
}
186+
})
187+
.unwrap();
188+
assert_eq!(attempts, 2);
189+
}
190+
191+
#[test]
192+
fn test_retry_failure() {
193+
let mut attempts = 0;
194+
assert!(retry(2, || {
195+
attempts += 1;
196+
Err(Error::Download(io::ErrorKind::Interrupted.into()))
197+
})
198+
.is_err());
199+
assert_eq!(attempts, 3);
200+
}
201+
}

0 commit comments

Comments
 (0)