11use crate :: { Error , Source } ;
22use log:: info;
33use sha2:: { Digest , Sha256 } ;
4- use std:: fs;
54use std:: io:: { self , Cursor , Read } ;
65use std:: path:: { Path , PathBuf } ;
6+ use std:: time:: Duration ;
7+ use std:: { fs, thread} ;
78use tar:: Archive ;
89use 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).
1415const 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.
1721pub ( 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+
85122fn 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