-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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 | | ||
|
||
| 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); | ||
|
||
| 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
Osis 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
Neterror butOsis what it printed.