-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Network retry issue 1602 #2396
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
Network retry issue 1602 #2396
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ use std::str; | |
use std::string; | ||
|
||
use curl; | ||
use curl_sys; | ||
use git2; | ||
use rustc_serialize::json; | ||
use semver; | ||
|
@@ -285,6 +286,36 @@ impl From<Box<CargoError>> for CliError { | |
} | ||
} | ||
|
||
// ============================================================================= | ||
// NetworkError trait | ||
|
||
pub trait NetworkError: CargoError { | ||
fn maybe_spurious(&self) -> bool; | ||
} | ||
|
||
impl NetworkError for git2::Error { | ||
fn maybe_spurious(&self) -> bool { | ||
match self.class() { | ||
git2::ErrorClass::Net | | ||
git2::ErrorClass::Os => true, | ||
_ => false | ||
} | ||
} | ||
} | ||
impl NetworkError for curl::ErrCode { | ||
fn maybe_spurious(&self) -> bool { | ||
match self.code() { | ||
curl_sys::CURLcode::CURLE_COULDNT_CONNECT | | ||
curl_sys::CURLcode::CURLE_COULDNT_RESOLVE_PROXY | | ||
curl_sys::CURLcode::CURLE_COULDNT_RESOLVE_HOST | | ||
curl_sys::CURLcode::CURLE_OPERATION_TIMEDOUT | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like an operation we shouldn't retry by default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like curl retries (at least once) on CURLE_OPERATION_TIMEDOUT https://github.com/curl/curl/blob/b9728bca549709a26a5228f1d44f7488dd26811d/src/tool_operate.c#L1449 I do not have any data around which errors people are seeing. It looks like, after a quick search in the curl code, that CURLE_OPERATION_TIMEDOUT covers a lot of different case. After this is merge the list can be changed easy. In this case I believe catching more is better only because if this error code turns out to be bad the work around would be to configure the setting. If you release cargo without this and it turns out that it is the core failure case for curl everyone who is affected need to wait for a new release cycle. Let me know if you would still like to remove it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, let's leave it in and we can always back it out if weird things happen |
||
curl_sys::CURLcode::CURLE_RECV_ERROR | ||
=> true, | ||
_ => false | ||
} | ||
} | ||
} | ||
|
||
// ============================================================================= | ||
// various impls | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
use util::{CargoResult, Config, errors}; | ||
|
||
/// Wrapper method for network call retry logic. | ||
/// | ||
/// Retry counts provided by Config object 'net.retry'. Config shell outputs | ||
/// a warning on per retry. | ||
/// | ||
/// Closure must return a CargoResult. | ||
/// | ||
/// Example: | ||
/// use util::network; | ||
/// cargo_result = network.with_retry(&config, || something.download()); | ||
pub fn with_retry<T, E, F>(config: &Config, mut callback: F) -> CargoResult<T> | ||
where F: FnMut() -> Result<T, E>, | ||
E: errors::NetworkError | ||
{ | ||
let mut remaining = try!(config.net_retry()); | ||
loop { | ||
match callback() { | ||
Ok(ret) => return Ok(ret), | ||
Err(ref e) if e.maybe_spurious() && remaining > 0 => { | ||
let msg = format!("spurious network error ({} tries \ | ||
remaining): {}", remaining, e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the remaining count here is actually There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The retry config is 2 by default.
The retry count is the count of retries not the count of total tries. I can subtract one from the config or update the docs to be explicitly clear it is the count of retries and not the total network tries? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah right yeah that makes sense, nevermind! |
||
try!(config.shell().warn(msg)); | ||
remaining -= 1; | ||
} | ||
Err(e) => return Err(Box::new(e)), | ||
} | ||
} | ||
} | ||
#[test] | ||
fn with_retry_repeats_the_call_then_works() { | ||
|
||
use std::error::Error; | ||
use util::human; | ||
use std::fmt; | ||
|
||
#[derive(Debug)] | ||
struct NetworkRetryError { | ||
error: Box<errors::CargoError>, | ||
} | ||
|
||
impl Error for NetworkRetryError { | ||
fn description(&self) -> &str { | ||
self.error.description() | ||
} | ||
fn cause(&self) -> Option<&Error> { | ||
self.error.cause() | ||
} | ||
} | ||
|
||
impl NetworkRetryError { | ||
fn new(error: &str) -> NetworkRetryError { | ||
let error = human(error.to_string()); | ||
NetworkRetryError { | ||
error: error, | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Display for NetworkRetryError { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
fmt::Display::fmt(&self.error, f) | ||
} | ||
} | ||
|
||
impl errors::CargoError for NetworkRetryError { | ||
fn is_human(&self) -> bool { | ||
false | ||
} | ||
} | ||
|
||
impl errors::NetworkError for NetworkRetryError { | ||
fn maybe_spurious(&self) -> bool { | ||
true | ||
} | ||
} | ||
|
||
let error1 = NetworkRetryError::new("one"); | ||
let error2 = NetworkRetryError::new("two"); | ||
let mut results: Vec<Result<(), NetworkRetryError>> = vec![Ok(()), | ||
Err(error1), Err(error2)]; | ||
let config = Config::default().unwrap(); | ||
let result = with_retry(&config, || results.pop().unwrap()); | ||
assert_eq!(result.unwrap(), ()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come
Os
is included here? In theory that's not spurious?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the class I got for my test when it timed out. When I tell the test to connect to a server that is not running https://github.com/rust-lang/cargo/pull/2396/files#diff-5a6267705b81a22950ae48ebdce1a553R8
I thought it would have gave a
Net
error butOs
is what it printed.