summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Whitaker <17734409+nathanwhit@users.noreply.github.com>2024-10-15 16:46:42 -0700
committerGitHub <noreply@github.com>2024-10-15 16:46:42 -0700
commit7c3c13cecf48219cdcb90dc0b5019686cdd88626 (patch)
treeea44361a7b8c061ec84ec29d88aa2c6a1c3ec07a
parent40b1c42138c47e89eefa859cd36a9e3d62541e7b (diff)
fix(install): retry downloads of registry info / tarballs (#26278)
Fixes #26085. Adds a basic retry utility with some defaults, starts off with a 100ms wait, then 250ms, then 500ms I've applied the retry in the http client, reusing an existing function, so this also applies to retrying downloads of deno binaries in `upgrade` and `compile`. I can make a separate function that doesn't retry so this doesn't affect `upgrade` and `compile`, but it seemed desirable to have retries there too, so I left it in.
-rw-r--r--cli/http_util.rs16
-rw-r--r--cli/npm/managed/cache/registry_info.rs11
-rw-r--r--cli/npm/managed/cache/tarball.rs2
-rw-r--r--cli/standalone/binary.rs6
-rw-r--r--cli/tools/upgrade.rs2
-rw-r--r--cli/util/mod.rs1
-rw-r--r--cli/util/retry.rs41
7 files changed, 68 insertions, 11 deletions
diff --git a/cli/http_util.rs b/cli/http_util.rs
index cf244c525..9c9ae9e41 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<Option<Vec<u8>>, 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 28b19373e..597a2283f 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 4bcee38ea..7cf88d6d6 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 6e747bed4..52ee4eeb2 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 7f21e6649..b1b09d1a6 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 e59b09d2c..f81a74c44 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 000000000..a8febe60d
--- /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<Output = Result<T, E>>,
+ ShouldRetry: FnMut(&E) -> bool,
+>(
+ mut f: F,
+ mut should_retry: ShouldRetry,
+) -> impl Future<Output = Result<T, E>> {
+ 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;
+ }
+ }
+}