blob: 795990e21132cfffe59f397b7b6dc1b3aebc7f0e [file] [log] [blame]
// 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/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.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(
std::unique_ptr<net::URLRequest> request,
std::unique_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_delegate(this);
}
AsyncRevalidationDriver::~AsyncRevalidationDriver() {}
void AsyncRevalidationDriver::StartRequest() {
// 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();
CancelRequestInternal(net::ERR_ABORTED, RESULT_GOT_REDIRECT);
}
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, RESULT_AUTH_FAILED);
}
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()) {
UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AsyncRevalidation.ResponseError",
-request_->status().ToNetError());
ResponseCompleted(RESULT_NET_ERROR);
// |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(RESULT_REVALIDATED);
// |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), RESULT_BODY_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 == 0 is EOF.
if (bytes_read == 0) {
ResponseCompleted(RESULT_LOADED);
return;
}
// bytes_read == -1 is an error.
if (bytes_read == -1 || !request_->status().is_success()) {
UMA_HISTOGRAM_SPARSE_SLOWLY("Net.AsyncRevalidation.ReadError",
-request_->status().ToNetError());
ResponseCompleted(RESULT_READ_ERROR);
// |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());
// 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), RESULT_RESPONSE_TIMEOUT));
request_->Start();
}
void AsyncRevalidationDriver::CancelRequestInternal(
int error,
AsyncRevalidationResult result) {
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(result);
// |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(
AsyncRevalidationResult result) {
DVLOG(1) << "ResponseCompleted: "
<< (request_ ? request_->url().spec() : "(request deleted)")
<< "result = " << result;
UMA_HISTOGRAM_ENUMERATION("Net.AsyncRevalidation.Result", result, RESULT_MAX);
DCHECK(!completion_callback_.is_null());
base::ResetAndReturn(&completion_callback_).Run();
// |this| may be deleted after this point.
}
void AsyncRevalidationDriver::OnTimeout(AsyncRevalidationResult result) {
CancelRequestInternal(net::ERR_TIMED_OUT, result);
}
void AsyncRevalidationDriver::RecordDefer() {
request_->LogBlockedBy(throttle_->GetNameForLogging());
DCHECK(!is_deferred_);
is_deferred_ = true;
}
} // namespace content