Skip to content

Automatically retry HTTP requests returning 5xx #4032

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions src/cargo/sources/registry/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use util::network;
use util::paths;
use util::{FileLock, Filesystem};
use util::{Config, CargoResult, ChainError, human, Sha256, ToUrl};
use util::errors::HttpError;

pub struct RemoteRegistry<'cfg> {
index_path: Filesystem,
Expand Down Expand Up @@ -153,26 +154,32 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
// TODO: don't download into memory, but ensure that if we ctrl-c a
// download we should resume either from the start or the middle
// on the next time
let url = url.to_string();
handle.get(true)?;
handle.url(&url.to_string())?;
handle.url(&url)?;
handle.follow_location(true)?;
let mut state = Sha256::new();
let mut body = Vec::new();
network::with_retry(self.config, || {
state = Sha256::new();
body = Vec::new();
let mut handle = handle.transfer();
handle.write_function(|buf| {
state.update(buf);
body.extend_from_slice(buf);
Ok(buf.len())
})?;
handle.perform()
{
let mut handle = handle.transfer();
handle.write_function(|buf| {
state.update(buf);
body.extend_from_slice(buf);
Ok(buf.len())
})?;
handle.perform()?;
}
let code = handle.response_code()?;
if code != 200 && code != 0 {
let url = handle.effective_url()?.unwrap_or(&url);
Err(HttpError::Not200(code, url.to_string()))
} else {
Ok(())
}
})?;
let code = handle.response_code()?;
if code != 200 && code != 0 {
bail!("failed to get 200 response from `{}`, got {}", url, code)
}

// Verify what we just downloaded
if state.finish().to_hex() != checksum {
Expand Down
58 changes: 58 additions & 0 deletions src/cargo/util/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ impl NetworkError for git2::Error {
}
}
}

impl NetworkError for curl::Error {
fn maybe_spurious(&self) -> bool {
self.is_couldnt_connect() ||
Expand All @@ -344,6 +345,63 @@ impl NetworkError for curl::Error {
}
}

#[derive(Debug)]
pub enum HttpError {
Not200(u32, String),
Curl(curl::Error),
}

impl fmt::Display for HttpError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
HttpError::Not200(code, ref url) => {
write!(f, "failed to get 200 response from `{}`, got {}",
url, code)
}
HttpError::Curl(ref e) => e.fmt(f),
}
}
}

impl Error for HttpError {
fn description(&self) -> &str {
match *self {
HttpError::Not200(..) => "failed to get a 200 response",
HttpError::Curl(ref e) => e.description(),
}
}

fn cause(&self) -> Option<&Error> {
match *self {
HttpError::Not200(..) => None,
HttpError::Curl(ref e) => e.cause(),
}
}
}

impl CargoError for HttpError {
fn is_human(&self) -> bool {
true
}
}

impl NetworkError for HttpError {
fn maybe_spurious(&self) -> bool {
match *self {
HttpError::Not200(code, ref _url) => {
500 <= code && code < 600
}
HttpError::Curl(ref e) => e.maybe_spurious(),
}
}
}

impl From<curl::Error> for HttpError {
fn from(err: curl::Error) -> HttpError {
HttpError::Curl(err)
}
}

// =============================================================================
// various impls

Expand Down