Index: net/url_request/url_request_http_job.cc |
=================================================================== |
--- net/url_request/url_request_http_job.cc (revision 152798) |
+++ net/url_request/url_request_http_job.cc (working copy) |
@@ -49,6 +49,17 @@ |
namespace net { |
+namespace { |
+ |
+// Array of all network error codes. Needed for histograms. |
+const int kAllNetErrorCodes[] = { |
+#define NET_ERROR(label, value) -(value), |
+#include "net/base/net_error_list.h" |
+#undef NET_ERROR |
+}; |
+ |
+} // namespace |
+ |
class URLRequestHttpJob::HttpFilterContext : public FilterContext { |
public: |
explicit HttpFilterContext(URLRequestHttpJob* job); |
@@ -244,7 +255,9 @@ |
base::Bind(&URLRequestHttpJob::OnHeadersReceivedCallback, |
base::Unretained(this)))), |
awaiting_callback_(false), |
- http_transaction_delegate_(new HttpTransactionDelegateImpl(request)) { |
+ http_transaction_delegate_(new HttpTransactionDelegateImpl(request)), |
+ has_retried_(false), |
+ error_before_retry_(OK) { |
URLRequestThrottlerManager* manager = request->context()->throttler_manager(); |
if (manager) |
throttling_entry_ = manager->RegisterRequestUrl(request->url()); |
@@ -757,6 +770,14 @@ |
const URLRequestContext* context = request_->context(); |
+ // If this was a retry, record whether or not we successfully connected to |
+ // the server on the second try. |
+ if (error_before_retry_ != OK) { |
+ DCHECK(has_retried_); |
+ RecordRetryResult(result); |
+ error_before_retry_ = OK; |
+ } |
+ |
if (result == ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN && |
transaction_->GetResponseInfo() != NULL) { |
FraudulentCertificateReporter* reporter = |
@@ -813,6 +834,8 @@ |
} else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) { |
NotifyCertificateRequested( |
transaction_->GetResponseInfo()->cert_request_info); |
+ } else if (ShouldRetryRequest(result)) { |
+ RetryRequestOnError(result); |
} else { |
NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result)); |
} |
@@ -864,6 +887,78 @@ |
AddCookieHeaderAndStart(); |
} |
+void URLRequestHttpJob::RetryRequestOnError(int result) { |
+ // Should have a transaction that returned the result, but should not have |
+ // received headers yet. |
+ DCHECK(transaction_.get()); |
+ DCHECK(!response_info_); |
+ DCHECK(result != OK && result != ERR_IO_PENDING); |
+ |
+ error_before_retry_ = result; |
+ has_retried_ = true; |
+ ResetTimer(); |
+ transaction_.reset(); |
+ |
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0)); |
+ StartTransactionInternal(); |
+} |
+ |
+bool URLRequestHttpJob::ShouldRetryRequest(int result) const { |
+ // Request must not have been cancelled, and no response info may have been |
+ // received yet. |
+ DCHECK(transaction_.get()); |
+ DCHECK(request_); |
+ DCHECK(!response_info_); |
+ |
+ // Don't retry if the request has already been retried or it's not a GET. |
+ if (has_retried_ || request_->method() != "GET") { |
+ return false; |
+ } |
+ |
+ |
+ // If the HTTP job has already taken too long, do not retry the request. |
+ base::TimeDelta request_duration = base::TimeTicks::Now() - start_time_; |
+ if (request_duration >= base::TimeDelta::FromSeconds(10)) |
+ return false; |
+ |
+ // TODO(mmenke): Look at the metrics and figure out which, if any, of these |
+ // error codes it's worth retrying on. |
+ switch (result) { |
+ // These four error codes will also result in a retry in NetworkTransaction, |
+ // in the case of a reused socket. It's unclear how much correlation there |
+ // is between receiving one of these errors on a reused socket and on a |
+ // fresh socket on retry. |
+ case ERR_CONNECTION_RESET: |
+ case ERR_CONNECTION_CLOSED: |
+ case ERR_CONNECTION_ABORTED: |
+ case ERR_SOCKET_NOT_CONNECTED: |
+ |
+ // System errors. |
+ case ERR_FAILED: |
+ case ERR_UNEXPECTED: |
+ |
+ // Connection errors. |
+ case ERR_CONNECTION_REFUSED: |
+ case ERR_CONNECTION_FAILED: |
+ case ERR_NAME_NOT_RESOLVED: |
+ case ERR_INTERNET_DISCONNECTED: |
+ case ERR_ADDRESS_UNREACHABLE: |
+ case ERR_NETWORK_ACCESS_DENIED: |
+ case ERR_ADDRESS_IN_USE: |
+ |
+ // Content errors. |
+ case ERR_EMPTY_RESPONSE: |
+ |
+ // Cache errors. |
+ case ERR_CACHE_MISS: |
+ case ERR_CACHE_READ_FAILURE: |
+ return true; |
+ |
+ default: |
+ return false; |
+ } |
+} |
+ |
void URLRequestHttpJob::SetUpload(UploadData* upload) { |
DCHECK(!transaction_.get()) << "cannot change once started"; |
request_info_.upload_data = upload; |
@@ -1317,6 +1412,38 @@ |
request_creation_time_ = base::Time::Now(); |
} |
+void URLRequestHttpJob::RecordRetryResult(int result) const { |
+ if (request_info_.load_flags & LOAD_MAIN_FRAME) { |
+ if (result != OK) { |
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION( |
+ "Net.NetworkErrorsRecovered.MainFrame", |
+ -error_before_retry_, |
+ base::CustomHistogram::ArrayToCustomRanges( |
+ kAllNetErrorCodes, arraysize(kAllNetErrorCodes))); |
+ } else { |
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION( |
+ "Net.NetworkErrorsUnrecovered.MainFrame", |
+ -error_before_retry_, |
+ base::CustomHistogram::ArrayToCustomRanges( |
+ kAllNetErrorCodes, arraysize(kAllNetErrorCodes))); |
+ } |
+ } else { |
+ if (result != OK) { |
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION( |
+ "Net.NetworkErrorsRecovered.Subresource", |
+ -error_before_retry_, |
+ base::CustomHistogram::ArrayToCustomRanges( |
+ kAllNetErrorCodes, arraysize(kAllNetErrorCodes))); |
+ } else { |
+ UMA_HISTOGRAM_CUSTOM_ENUMERATION( |
+ "Net.NetworkErrorsUnrecovered.Subresource", |
+ -error_before_retry_, |
+ base::CustomHistogram::ArrayToCustomRanges( |
+ kAllNetErrorCodes, arraysize(kAllNetErrorCodes))); |
+ } |
+ } |
+} |
+ |
void URLRequestHttpJob::UpdatePacketReadTimes() { |
if (!packet_timing_enabled_) |
return; |