mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 07:14:47 -05:00
fix: retry module download once if server errored (#17252)
Closes https://github.com/denoland/deno/issues/17251 Closes #19970 This commits adds logic to retry failed module downloads once. Both request and server errors are handled and the retry is done after 50 ms wait time.
This commit is contained in:
parent
a05e890d56
commit
5df2b0b4dc
3 changed files with 145 additions and 39 deletions
|
@ -383,44 +383,84 @@ impl FileFetcher {
|
||||||
let specifier = specifier.clone();
|
let specifier = specifier.clone();
|
||||||
let client = self.http_client.clone();
|
let client = self.http_client.clone();
|
||||||
let file_fetcher = self.clone();
|
let file_fetcher = self.clone();
|
||||||
// A single pass of fetch either yields code or yields a redirect.
|
// A single pass of fetch either yields code or yields a redirect, server
|
||||||
|
// error causes a single retry to avoid crashing hard on intermittent failures.
|
||||||
|
|
||||||
|
async fn handle_request_or_server_error(
|
||||||
|
retried: &mut bool,
|
||||||
|
specifier: &Url,
|
||||||
|
err_str: String,
|
||||||
|
) -> Result<(), AnyError> {
|
||||||
|
// Retry once, and bail otherwise.
|
||||||
|
if !*retried {
|
||||||
|
*retried = true;
|
||||||
|
log::debug!("Import '{}' failed: {}. Retrying...", specifier, err_str);
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(generic_error(format!(
|
||||||
|
"Import '{}' failed: {}",
|
||||||
|
specifier, err_str
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async move {
|
async move {
|
||||||
let result = match fetch_once(
|
let mut retried = false;
|
||||||
&client,
|
let result = loop {
|
||||||
FetchOnceArgs {
|
let result = match fetch_once(
|
||||||
url: specifier.clone(),
|
&client,
|
||||||
maybe_accept: maybe_accept.clone(),
|
FetchOnceArgs {
|
||||||
maybe_etag,
|
url: specifier.clone(),
|
||||||
maybe_auth_token,
|
maybe_accept: maybe_accept.clone(),
|
||||||
maybe_progress_guard: maybe_progress_guard.as_ref(),
|
maybe_etag: maybe_etag.clone(),
|
||||||
},
|
maybe_auth_token: maybe_auth_token.clone(),
|
||||||
)
|
maybe_progress_guard: maybe_progress_guard.as_ref(),
|
||||||
.await?
|
},
|
||||||
{
|
)
|
||||||
FetchOnceResult::NotModified => {
|
.await?
|
||||||
let file = file_fetcher.fetch_cached(&specifier, 10)?.unwrap();
|
{
|
||||||
Ok(file)
|
FetchOnceResult::NotModified => {
|
||||||
}
|
let file = file_fetcher.fetch_cached(&specifier, 10)?.unwrap();
|
||||||
FetchOnceResult::Redirect(redirect_url, headers) => {
|
Ok(file)
|
||||||
file_fetcher.http_cache.set(&specifier, headers, &[])?;
|
}
|
||||||
file_fetcher
|
FetchOnceResult::Redirect(redirect_url, headers) => {
|
||||||
.fetch_remote(
|
file_fetcher.http_cache.set(&specifier, headers, &[])?;
|
||||||
&redirect_url,
|
file_fetcher
|
||||||
permissions,
|
.fetch_remote(
|
||||||
redirect_limit - 1,
|
&redirect_url,
|
||||||
maybe_accept,
|
permissions,
|
||||||
|
redirect_limit - 1,
|
||||||
|
maybe_accept,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
FetchOnceResult::Code(bytes, headers) => {
|
||||||
|
file_fetcher
|
||||||
|
.http_cache
|
||||||
|
.set(&specifier, headers.clone(), &bytes)?;
|
||||||
|
let file =
|
||||||
|
file_fetcher.build_remote_file(&specifier, bytes, &headers)?;
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
FetchOnceResult::RequestError(err) => {
|
||||||
|
handle_request_or_server_error(&mut retried, &specifier, err)
|
||||||
|
.await?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
FetchOnceResult::ServerError(status) => {
|
||||||
|
handle_request_or_server_error(
|
||||||
|
&mut retried,
|
||||||
|
&specifier,
|
||||||
|
status.to_string(),
|
||||||
)
|
)
|
||||||
.await
|
.await?;
|
||||||
}
|
continue;
|
||||||
FetchOnceResult::Code(bytes, headers) => {
|
}
|
||||||
file_fetcher
|
};
|
||||||
.http_cache
|
break result;
|
||||||
.set(&specifier, headers.clone(), &bytes)?;
|
|
||||||
let file =
|
|
||||||
file_fetcher.build_remote_file(&specifier, bytes, &headers)?;
|
|
||||||
Ok(file)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
drop(maybe_progress_guard);
|
drop(maybe_progress_guard);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -572,6 +612,8 @@ enum FetchOnceResult {
|
||||||
Code(Vec<u8>, HeadersMap),
|
Code(Vec<u8>, HeadersMap),
|
||||||
NotModified,
|
NotModified,
|
||||||
Redirect(Url, HeadersMap),
|
Redirect(Url, HeadersMap),
|
||||||
|
RequestError(String),
|
||||||
|
ServerError(StatusCode),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -606,7 +648,15 @@ async fn fetch_once<'a>(
|
||||||
let accepts_val = HeaderValue::from_str(&accept)?;
|
let accepts_val = HeaderValue::from_str(&accept)?;
|
||||||
request = request.header(ACCEPT, accepts_val);
|
request = request.header(ACCEPT, accepts_val);
|
||||||
}
|
}
|
||||||
let response = request.send().await?;
|
let response = match request.send().await {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(err) => {
|
||||||
|
if err.is_connect() || err.is_timeout() {
|
||||||
|
return Ok(FetchOnceResult::RequestError(err.to_string()));
|
||||||
|
}
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if response.status() == StatusCode::NOT_MODIFIED {
|
if response.status() == StatusCode::NOT_MODIFIED {
|
||||||
return Ok(FetchOnceResult::NotModified);
|
return Ok(FetchOnceResult::NotModified);
|
||||||
|
@ -639,8 +689,13 @@ async fn fetch_once<'a>(
|
||||||
return Ok(FetchOnceResult::Redirect(new_url, result_headers));
|
return Ok(FetchOnceResult::Redirect(new_url, result_headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
if response.status().is_client_error() || response.status().is_server_error()
|
let status = response.status();
|
||||||
{
|
|
||||||
|
if status.is_server_error() {
|
||||||
|
return Ok(FetchOnceResult::ServerError(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
if status.is_client_error() {
|
||||||
let err = if response.status() == StatusCode::NOT_FOUND {
|
let err = if response.status() == StatusCode::NOT_FOUND {
|
||||||
custom_error(
|
custom_error(
|
||||||
"NotFound",
|
"NotFound",
|
||||||
|
@ -2230,4 +2285,50 @@ mod tests {
|
||||||
// Check that the error message contains the original URL
|
// Check that the error message contains the original URL
|
||||||
assert!(err.to_string().contains(url_str));
|
assert!(err.to_string().contains(url_str));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn server_error() {
|
||||||
|
let _g = test_util::http_server();
|
||||||
|
let url_str = "http://127.0.0.1:4545/server_error";
|
||||||
|
let url = Url::parse(url_str).unwrap();
|
||||||
|
let client = create_test_client();
|
||||||
|
let result = fetch_once(
|
||||||
|
&client,
|
||||||
|
FetchOnceArgs {
|
||||||
|
url,
|
||||||
|
maybe_accept: None,
|
||||||
|
maybe_etag: None,
|
||||||
|
maybe_auth_token: None,
|
||||||
|
maybe_progress_guard: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Ok(FetchOnceResult::ServerError(status)) = result {
|
||||||
|
assert_eq!(status, 500);
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn request_error() {
|
||||||
|
let _g = test_util::http_server();
|
||||||
|
let url_str = "http://127.0.0.1:9999/";
|
||||||
|
let url = Url::parse(url_str).unwrap();
|
||||||
|
let client = create_test_client();
|
||||||
|
let result = fetch_once(
|
||||||
|
&client,
|
||||||
|
FetchOnceArgs {
|
||||||
|
url,
|
||||||
|
maybe_accept: None,
|
||||||
|
maybe_etag: None,
|
||||||
|
maybe_auth_token: None,
|
||||||
|
maybe_progress_guard: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(matches!(result, Ok(FetchOnceResult::RequestError(_))));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
DANGER: TLS certificate validation is disabled for: deno.land
|
DANGER: TLS certificate validation is disabled for: deno.land
|
||||||
error: error sending request for url (https://localhost:5545/subdir/mod2.ts): error trying to connect: invalid peer certificate: UnknownIssuer
|
error: Import 'https://localhost:5545/subdir/mod2.ts' failed: error sending request for url (https://localhost:5545/subdir/mod2.ts): error trying to connect: invalid peer certificate: UnknownIssuer
|
||||||
at file:///[WILDCARD]/cafile_url_imports.ts:[WILDCARD]
|
at file:///[WILDCARD]/cafile_url_imports.ts:[WILDCARD]
|
||||||
|
|
|
@ -808,6 +808,11 @@ async fn main_server(
|
||||||
*res.status_mut() = StatusCode::FOUND;
|
*res.status_mut() = StatusCode::FOUND;
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
(_, "/server_error") => {
|
||||||
|
let mut res = Response::new(Body::empty());
|
||||||
|
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
(_, "/x_deno_warning.js") => {
|
(_, "/x_deno_warning.js") => {
|
||||||
let mut res = Response::new(Body::empty());
|
let mut res = Response::new(Body::empty());
|
||||||
*res.status_mut() = StatusCode::MOVED_PERMANENTLY;
|
*res.status_mut() = StatusCode::MOVED_PERMANENTLY;
|
||||||
|
|
Loading…
Reference in a new issue