diff --git a/cli/http_util.rs b/cli/http_util.rs index cf244c525a81b1..9c9ae9e413f384 100644 --- a/cli/http_util.rs +++ b/cli/http_util.rs @@ -470,15 +470,23 @@ impl HttpClient { } } - pub async fn download_with_progress( + pub async fn download_with_progress_and_retries( &self, url: Url, maybe_header: Option<(HeaderName, HeaderValue)>, progress_guard: &UpdateGuard, ) -> Result>, DownloadError> { - self - .download_inner(url, maybe_header, Some(progress_guard)) - .await + crate::util::retry::retry( + || { + self.download_inner( + url.clone(), + maybe_header.clone(), + Some(progress_guard), + ) + }, + |e| matches!(e, DownloadError::BadResponse(_) | DownloadError::Fetch(_)), + ) + .await } pub async fn get_redirected_url( diff --git a/cli/npm/managed/cache/registry_info.rs b/cli/npm/managed/cache/registry_info.rs index 28b19373e918e0..597a2283f48732 100644 --- a/cli/npm/managed/cache/registry_info.rs +++ b/cli/npm/managed/cache/registry_info.rs @@ -202,10 +202,13 @@ impl RegistryInfoDownloader { let guard = self.progress_bar.update(package_url.as_str()); let name = name.to_string(); async move { - let maybe_bytes = downloader - .http_client_provider - .get_or_create()? - .download_with_progress(package_url, maybe_auth_header, &guard) + let client = downloader.http_client_provider.get_or_create()?; + let maybe_bytes = client + .download_with_progress_and_retries( + package_url, + maybe_auth_header, + &guard, + ) .await?; match maybe_bytes { Some(bytes) => { diff --git a/cli/npm/managed/cache/tarball.rs b/cli/npm/managed/cache/tarball.rs index 4bcee38ea85d8c..7cf88d6d64d54e 100644 --- a/cli/npm/managed/cache/tarball.rs +++ b/cli/npm/managed/cache/tarball.rs @@ -172,7 +172,7 @@ impl TarballCache { let guard = tarball_cache.progress_bar.update(&dist.tarball); let result = tarball_cache.http_client_provider .get_or_create()? - .download_with_progress(tarball_uri, maybe_auth_header, &guard) + .download_with_progress_and_retries(tarball_uri, maybe_auth_header, &guard) .await; let maybe_bytes = match result { Ok(maybe_bytes) => maybe_bytes, diff --git a/cli/standalone/binary.rs b/cli/standalone/binary.rs index 6e747bed42eb64..52ee4eeb28671d 100644 --- a/cli/standalone/binary.rs +++ b/cli/standalone/binary.rs @@ -468,7 +468,11 @@ impl<'a> DenoCompileBinaryWriter<'a> { self .http_client_provider .get_or_create()? - .download_with_progress(download_url.parse()?, None, &progress) + .download_with_progress_and_retries( + download_url.parse()?, + None, + &progress, + ) .await? }; let bytes = match maybe_bytes { diff --git a/cli/tools/upgrade.rs b/cli/tools/upgrade.rs index 7f21e6649c2c5b..b1b09d1a6f6cb3 100644 --- a/cli/tools/upgrade.rs +++ b/cli/tools/upgrade.rs @@ -913,7 +913,7 @@ async fn download_package( // text above which will stay alive after the progress bars are complete let progress = progress_bar.update(""); let maybe_bytes = client - .download_with_progress(download_url.clone(), None, &progress) + .download_with_progress_and_retries(download_url.clone(), None, &progress) .await .with_context(|| format!("Failed downloading {download_url}. The version you requested may not have been built for the current architecture."))?; Ok(maybe_bytes) diff --git a/cli/util/mod.rs b/cli/util/mod.rs index e59b09d2c7167e..f81a74c449d026 100644 --- a/cli/util/mod.rs +++ b/cli/util/mod.rs @@ -14,6 +14,7 @@ pub mod logger; pub mod path; pub mod progress_bar; pub mod result; +pub mod retry; pub mod sync; pub mod text_encoding; pub mod unix; diff --git a/cli/util/retry.rs b/cli/util/retry.rs new file mode 100644 index 00000000000000..a8febe60dedf33 --- /dev/null +++ b/cli/util/retry.rs @@ -0,0 +1,41 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::future::Future; +use std::time::Duration; + +pub fn retry< + F: FnMut() -> Fut, + T, + E, + Fut: Future>, + ShouldRetry: FnMut(&E) -> bool, +>( + mut f: F, + mut should_retry: ShouldRetry, +) -> impl Future> { + const WAITS: [Duration; 3] = [ + Duration::from_millis(100), + Duration::from_millis(250), + Duration::from_millis(500), + ]; + + let mut waits = WAITS.into_iter(); + async move { + let mut first_result = None; + loop { + let result = f().await; + match result { + Ok(r) => return Ok(r), + Err(e) if !should_retry(&e) => return Err(e), + _ => {} + } + if first_result.is_none() { + first_result = Some(result); + } + let Some(wait) = waits.next() else { + return first_result.unwrap(); + }; + tokio::time::sleep(wait).await; + } + } +}