| Index: content/browser/loader/async_revalidation_driver.cc | 
| diff --git a/content/browser/loader/async_revalidation_driver.cc b/content/browser/loader/async_revalidation_driver.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..2007a186ebf315cef594fe4d3226a1d43058c05f | 
| --- /dev/null | 
| +++ b/content/browser/loader/async_revalidation_driver.cc | 
| @@ -0,0 +1,273 @@ | 
| +// Copyright 2015 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "content/browser/loader/async_revalidation_driver.h" | 
| + | 
| +#include <utility> | 
| + | 
| +#include "base/callback_helpers.h" | 
| +#include "base/location.h" | 
| +#include "base/logging.h" | 
| +#include "base/memory/ref_counted.h" | 
| +#include "base/metrics/user_metrics_action.h" | 
| +#include "base/single_thread_task_runner.h" | 
| +#include "base/thread_task_runner_handle.h" | 
| +#include "base/time/time.h" | 
| +#include "content/public/browser/user_metrics.h" | 
| +#include "net/base/net_errors.h" | 
| +#include "net/url_request/url_request_status.h" | 
| + | 
| +namespace content { | 
| + | 
| +namespace { | 
| +// This matches the maximum allocation size of AsyncResourceHandler. | 
| +const int kReadBufSize = 32 * 1024; | 
| + | 
| +// The time to wait for a response. Since this includes the time taken to | 
| +// connect, this has been set to match the connect timeout | 
| +// kTransportConnectJobTimeoutInSeconds. | 
| +const int kResponseTimeoutInSeconds = 240;  // 4 minutes. | 
| + | 
| +// This value should not be too large, as this request may be tying up a socket | 
| +// that could be used for something better. However, if it is too small, the | 
| +// cache entry will be truncated for no good reason. | 
| +// TODO(ricea): Find a more scientific way to set this timeout. | 
| +const int kReadTimeoutInSeconds = 30; | 
| +}  // namespace | 
| + | 
| +AsyncRevalidationDriver::AsyncRevalidationDriver( | 
| +    scoped_ptr<net::URLRequest> request, | 
| +    scoped_ptr<ResourceThrottle> throttle, | 
| +    const base::Closure& completion_callback) | 
| +    : request_(std::move(request)), | 
| +      throttle_(std::move(throttle)), | 
| +      completion_callback_(completion_callback), | 
| +      weak_ptr_factory_(this) { | 
| +  request_->set_delegate(this); | 
| +  throttle_->set_controller(this); | 
| +} | 
| + | 
| +AsyncRevalidationDriver::~AsyncRevalidationDriver() {} | 
| + | 
| +void AsyncRevalidationDriver::StartRequest() { | 
| +  RecordAction(base::UserMetricsAction("AsyncRevalidationCreated")); | 
| +  // Give the handler a chance to delay the URLRequest from being started. | 
| +  bool defer_start = false; | 
| +  throttle_->WillStartRequest(&defer_start); | 
| + | 
| +  if (defer_start) { | 
| +    RecordDefer(); | 
| +  } else { | 
| +    StartRequestInternal(); | 
| +  } | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::OnReceivedRedirect( | 
| +    net::URLRequest* request, | 
| +    const net::RedirectInfo& redirect_info, | 
| +    bool* defer) { | 
| +  DCHECK_EQ(request_.get(), request); | 
| + | 
| +  // The async revalidation should not follow redirects, because caching is | 
| +  // a property of an individual HTTP resource. | 
| +  DVLOG(1) << "OnReceivedRedirect: " << request_->url().spec(); | 
| +  RecordAction(base::UserMetricsAction("AsyncRevalidationRedirected")); | 
| +  CancelRequestInternal(net::ERR_ABORTED); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::OnAuthRequired( | 
| +    net::URLRequest* request, | 
| +    net::AuthChallengeInfo* auth_info) { | 
| +  DCHECK_EQ(request_.get(), request); | 
| +  // This error code doesn't have exactly the right semantics, but it should | 
| +  // be sufficient to narrow down the problem in net logs. | 
| +  CancelRequestInternal(net::ERR_ACCESS_DENIED); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::OnBeforeNetworkStart(net::URLRequest* request, | 
| +                                                   bool* defer) { | 
| +  DCHECK_EQ(request_.get(), request); | 
| + | 
| +  // Verify that the ResourceScheduler does not defer here. | 
| +  throttle_->WillStartUsingNetwork(defer); | 
| +  DCHECK(!*defer); | 
| + | 
| +  // Start the response timer. This use of base::Unretained() is guaranteed safe | 
| +  // by the semantics of base::OneShotTimer. | 
| +  timer_.Start(FROM_HERE, | 
| +               base::TimeDelta::FromSeconds(kResponseTimeoutInSeconds), | 
| +               base::Bind(&AsyncRevalidationDriver::OnTimeout, | 
| +                          base::Unretained(this), RESPONSE_TIMEOUT)); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::OnResponseStarted(net::URLRequest* request) { | 
| +  DCHECK_EQ(request_.get(), request); | 
| + | 
| +  DVLOG(1) << "OnResponseStarted: " << request_->url().spec(); | 
| + | 
| +  // We have the response. No need to wait any longer. | 
| +  timer_.Stop(); | 
| + | 
| +  if (!request_->status().is_success()) { | 
| +    ResponseCompleted(); | 
| +    // |this| may be deleted after this point. | 
| +    return; | 
| +  } | 
| + | 
| +  const net::HttpResponseInfo& response_info = request_->response_info(); | 
| +  if (!response_info.response_time.is_null() && response_info.was_cached) { | 
| +    // The cached entry was revalidated. No need to read it in. | 
| +    ResponseCompleted(); | 
| +    // |this| may be deleted after this point. | 
| +    return; | 
| +  } | 
| + | 
| +  bool defer = false; | 
| +  throttle_->WillProcessResponse(&defer); | 
| +  DCHECK(!defer); | 
| + | 
| +  // Set up the timer for reading the body. This use of base::Unretained() is | 
| +  // guaranteed safe by the semantics of base::OneShotTimer. | 
| +  timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kReadTimeoutInSeconds), | 
| +               base::Bind(&AsyncRevalidationDriver::OnTimeout, | 
| +                          base::Unretained(this), READ_TIMEOUT)); | 
| +  StartReading(false);  // Read the first chunk. | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::OnReadCompleted(net::URLRequest* request, | 
| +                                              int bytes_read) { | 
| +  // request_ could be NULL if a timeout happened while OnReadCompleted() was | 
| +  // queued to run asynchronously. | 
| +  if (!request_) | 
| +    return; | 
| +  DCHECK_EQ(request_.get(), request); | 
| +  DCHECK(!is_deferred_); | 
| +  DVLOG(1) << "OnReadCompleted: \"" << request_->url().spec() << "\"" | 
| +           << " bytes_read = " << bytes_read; | 
| + | 
| +  // bytes_read == -1 is an error. | 
| +  // bytes_read == 0 is EOF. | 
| +  if (bytes_read == -1 || bytes_read == 0 || !request_->status().is_success()) { | 
| +    ResponseCompleted(); | 
| +    // |this| may be deleted after this point. | 
| +    return; | 
| +  } | 
| + | 
| +  DCHECK_GT(bytes_read, 0); | 
| +  StartReading(true);  // Read the next chunk. | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::Resume() { | 
| +  DCHECK(is_deferred_); | 
| +  DCHECK(request_); | 
| +  is_deferred_ = false; | 
| +  request_->LogUnblocked(); | 
| +  StartRequestInternal(); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::Cancel() { | 
| +  NOTREACHED(); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::CancelAndIgnore() { | 
| +  NOTREACHED(); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::CancelWithError(int error_code) { | 
| +  NOTREACHED(); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::StartRequestInternal() { | 
| +  DCHECK(request_); | 
| +  DCHECK(!request_->is_pending()); | 
| + | 
| +  request_->Start(); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::CancelRequestInternal(int error) { | 
| +  DVLOG(1) << "CancelRequestInternal: " << request_->url().spec(); | 
| + | 
| +  // Set the error code since this will be reported to the NetworkDelegate and | 
| +  // recorded in the NetLog. | 
| +  request_->CancelWithError(error); | 
| + | 
| +  // The ResourceScheduler needs to be able to examine the request when the | 
| +  // ResourceThrottle is destroyed, so delete it first. | 
| +  throttle_.reset(); | 
| + | 
| +  // Destroy the request so that it doesn't try to send an asynchronous | 
| +  // notification of completion. | 
| +  request_.reset(); | 
| + | 
| +  // Cancel timer to prevent OnTimeout() being called. | 
| +  timer_.Stop(); | 
| + | 
| +  ResponseCompleted(); | 
| +  // |this| may deleted after this point. | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::StartReading(bool is_continuation) { | 
| +  int bytes_read = 0; | 
| +  ReadMore(&bytes_read); | 
| + | 
| +  // If IO is pending, wait for the URLRequest to call OnReadCompleted. | 
| +  if (request_->status().is_io_pending()) | 
| +    return; | 
| + | 
| +  if (!is_continuation || bytes_read <= 0) { | 
| +    OnReadCompleted(request_.get(), bytes_read); | 
| +  } else { | 
| +    // Else, trigger OnReadCompleted asynchronously to avoid starving the IO | 
| +    // thread in case the URLRequest can provide data synchronously. | 
| +    scoped_refptr<base::SingleThreadTaskRunner> single_thread_task_runner = | 
| +        base::ThreadTaskRunnerHandle::Get(); | 
| +    single_thread_task_runner->PostTask( | 
| +        FROM_HERE, | 
| +        base::Bind(&AsyncRevalidationDriver::OnReadCompleted, | 
| +                   weak_ptr_factory_.GetWeakPtr(), request_.get(), bytes_read)); | 
| +  } | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::ReadMore(int* bytes_read) { | 
| +  DCHECK(!is_deferred_); | 
| + | 
| +  if (!read_buffer_) | 
| +    read_buffer_ = new net::IOBuffer(kReadBufSize); | 
| + | 
| +  timer_.Reset(); | 
| +  request_->Read(read_buffer_.get(), kReadBufSize, bytes_read); | 
| + | 
| +  // No need to check the return value here as we'll detect errors by | 
| +  // inspecting the URLRequest's status. | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::ResponseCompleted() { | 
| +  DVLOG(1) << "ResponseCompleted: " << request_->url().spec(); | 
| +  DCHECK(!completion_callback_.is_null()); | 
| +  base::ResetAndReturn(&completion_callback_).Run(); | 
| +  // |this| may be deleted after this point. | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::OnTimeout(TimeoutType type) { | 
| +  // UserMetricsAction must be passed a literal string otherwise | 
| +  // //tools/metrics/actions/extract_actions.py won't understand it. | 
| +  switch (type) { | 
| +    case RESPONSE_TIMEOUT: | 
| +      RecordAction(base::UserMetricsAction("AsyncRevalidationResponseTimeout")); | 
| +      break; | 
| + | 
| +    case READ_TIMEOUT: | 
| +      RecordAction(base::UserMetricsAction("AsyncRevalidationReadTimeout")); | 
| +      break; | 
| +  } | 
| +  CancelRequestInternal(net::ERR_TIMED_OUT); | 
| +} | 
| + | 
| +void AsyncRevalidationDriver::RecordDefer() { | 
| +  request_->LogBlockedBy(throttle_->GetNameForLogging()); | 
| +  DCHECK(!is_deferred_); | 
| +  is_deferred_ = true; | 
| +} | 
| + | 
| +}  // namespace content | 
|  |