| Index: net/http/disk_cache_based_quic_server_info.cc
|
| diff --git a/net/http/disk_cache_based_quic_server_info.cc b/net/http/disk_cache_based_quic_server_info.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b149fc95ae211e39a78ef9fa35e31add15508f28
|
| --- /dev/null
|
| +++ b/net/http/disk_cache_based_quic_server_info.cc
|
| @@ -0,0 +1,450 @@
|
| +// Copyright 2014 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 "net/http/disk_cache_based_quic_server_info.h"
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/callback.h"
|
| +#include "base/callback_helpers.h"
|
| +#include "base/logging.h"
|
| +#include "base/metrics/histogram_macros.h"
|
| +#include "base/stl_util.h"
|
| +#include "base/trace_event/memory_usage_estimator.h"
|
| +#include "net/base/completion_callback.h"
|
| +#include "net/base/io_buffer.h"
|
| +#include "net/base/net_errors.h"
|
| +#include "net/http/http_cache.h"
|
| +#include "net/http/http_network_session.h"
|
| +#include "net/quic/core/quic_server_id.h"
|
| +
|
| +namespace net {
|
| +
|
| +// Some APIs inside disk_cache take a handle that the caller must keep alive
|
| +// until the API has finished its asynchronous execution.
|
| +//
|
| +// Unfortunately, DiskCacheBasedQuicServerInfo may be deleted before the
|
| +// operation completes causing a use-after-free.
|
| +//
|
| +// This data shim struct is meant to provide a location for the disk_cache
|
| +// APIs to write into even if the originating DiskCacheBasedQuicServerInfo
|
| +// object has been deleted. The lifetime for instances of this struct
|
| +// should be bound to the CompletionCallback that is passed to the disk_cache
|
| +// API. We do this by binding an instance of this struct to an unused
|
| +// parameter for OnIOComplete() using base::Owned().
|
| +//
|
| +// This is a hack. A better fix is to make it so that the disk_cache APIs
|
| +// take a Callback to a mutator for setting the output value rather than
|
| +// writing into a raw handle. Then the caller can just pass in a Callback
|
| +// bound to WeakPtr for itself. This callback would correctly "no-op" itself
|
| +// when the DiskCacheBasedQuicServerInfo object is deleted.
|
| +//
|
| +// TODO(ajwong): Change disk_cache's API to return results via Callback.
|
| +struct DiskCacheBasedQuicServerInfo::CacheOperationDataShim {
|
| + CacheOperationDataShim() : backend(NULL), entry(NULL) {}
|
| +
|
| + disk_cache::Backend* backend;
|
| + disk_cache::Entry* entry;
|
| +};
|
| +
|
| +DiskCacheBasedQuicServerInfo::DiskCacheBasedQuicServerInfo(
|
| + const QuicServerId& server_id,
|
| + HttpCache* http_cache)
|
| + : QuicServerInfo(server_id),
|
| + data_shim_(new CacheOperationDataShim()),
|
| + state_(GET_BACKEND),
|
| + ready_(false),
|
| + found_entry_(false),
|
| + server_id_(server_id),
|
| + http_cache_(http_cache),
|
| + backend_(NULL),
|
| + entry_(NULL),
|
| + last_failure_(NO_FAILURE),
|
| + weak_factory_(this) {
|
| + io_callback_ =
|
| + base::Bind(&DiskCacheBasedQuicServerInfo::OnIOComplete,
|
| + weak_factory_.GetWeakPtr(),
|
| + base::Owned(data_shim_)); // Ownership assigned.
|
| +}
|
| +
|
| +DiskCacheBasedQuicServerInfo::~DiskCacheBasedQuicServerInfo() {
|
| + DCHECK(wait_for_ready_callback_.is_null());
|
| + if (entry_)
|
| + entry_->Close();
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::Start() {
|
| + DCHECK(CalledOnValidThread());
|
| + DCHECK_EQ(GET_BACKEND, state_);
|
| + DCHECK_EQ(last_failure_, NO_FAILURE);
|
| + RecordQuicServerInfoStatus(QUIC_SERVER_INFO_START);
|
| + load_start_time_ = base::TimeTicks::Now();
|
| + DoLoop(OK);
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::WaitForDataReady(
|
| + const CompletionCallback& callback) {
|
| + DCHECK(CalledOnValidThread());
|
| + DCHECK_NE(GET_BACKEND, state_);
|
| + wait_for_data_start_time_ = base::TimeTicks::Now();
|
| +
|
| + RecordQuicServerInfoStatus(QUIC_SERVER_INFO_WAIT_FOR_DATA_READY);
|
| + if (ready_) {
|
| + wait_for_data_end_time_ = base::TimeTicks::Now();
|
| + RecordLastFailure();
|
| + return OK;
|
| + }
|
| +
|
| + if (!callback.is_null()) {
|
| + // Prevent a new callback for WaitForDataReady overwriting an existing
|
| + // pending callback (|wait_for_ready_callback_|).
|
| + if (!wait_for_ready_callback_.is_null()) {
|
| + RecordQuicServerInfoFailure(WAIT_FOR_DATA_READY_INVALID_ARGUMENT_FAILURE);
|
| + return ERR_INVALID_ARGUMENT;
|
| + }
|
| + wait_for_ready_callback_ = callback;
|
| + }
|
| +
|
| + return ERR_IO_PENDING;
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::ResetWaitForDataReadyCallback() {
|
| + DCHECK(CalledOnValidThread());
|
| + wait_for_ready_callback_.Reset();
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::CancelWaitForDataReadyCallback() {
|
| + DCHECK(CalledOnValidThread());
|
| +
|
| + RecordQuicServerInfoStatus(QUIC_SERVER_INFO_WAIT_FOR_DATA_READY_CANCEL);
|
| + if (!wait_for_ready_callback_.is_null()) {
|
| + RecordLastFailure();
|
| + wait_for_ready_callback_.Reset();
|
| + }
|
| +}
|
| +
|
| +bool DiskCacheBasedQuicServerInfo::IsDataReady() {
|
| + return ready_;
|
| +}
|
| +
|
| +bool DiskCacheBasedQuicServerInfo::IsReadyToPersist() {
|
| + // The data can be persisted if it has been loaded from the disk cache
|
| + // and there are no pending writes.
|
| + RecordQuicServerInfoStatus(QUIC_SERVER_INFO_READY_TO_PERSIST);
|
| + if (ready_ && new_data_.empty())
|
| + return true;
|
| + RecordQuicServerInfoFailure(READY_TO_PERSIST_FAILURE);
|
| + return false;
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::Persist() {
|
| + DCHECK(CalledOnValidThread());
|
| + if (!IsReadyToPersist()) {
|
| + // Handle updates while a write is pending or if we haven't loaded from disk
|
| + // cache. Save the data to be written into a temporary buffer and then
|
| + // persist that data when we are ready to persist.
|
| + pending_write_data_ = Serialize();
|
| + return;
|
| + }
|
| + PersistInternal();
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::PersistInternal() {
|
| + DCHECK(CalledOnValidThread());
|
| + DCHECK_NE(GET_BACKEND, state_);
|
| + DCHECK(new_data_.empty());
|
| + CHECK(ready_);
|
| + DCHECK(wait_for_ready_callback_.is_null());
|
| +
|
| + if (pending_write_data_.empty()) {
|
| + new_data_ = Serialize();
|
| + } else {
|
| + new_data_ = pending_write_data_;
|
| + base::STLClearObject(&pending_write_data_);
|
| + }
|
| +
|
| + RecordQuicServerInfoStatus(QUIC_SERVER_INFO_PERSIST);
|
| + if (!backend_) {
|
| + RecordQuicServerInfoFailure(PERSIST_NO_BACKEND_FAILURE);
|
| + return;
|
| + }
|
| +
|
| + state_ = CREATE_OR_OPEN;
|
| + DoLoop(OK);
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::OnExternalCacheHit() {
|
| + DCHECK(CalledOnValidThread());
|
| + DCHECK_NE(GET_BACKEND, state_);
|
| +
|
| + RecordQuicServerInfoStatus(QUIC_SERVER_INFO_EXTERNAL_CACHE_HIT);
|
| + if (!backend_) {
|
| + RecordQuicServerInfoFailure(PERSIST_NO_BACKEND_FAILURE);
|
| + return;
|
| + }
|
| +
|
| + backend_->OnExternalCacheHit(key());
|
| +}
|
| +
|
| +size_t DiskCacheBasedQuicServerInfo::EstimateMemoryUsage() const {
|
| + return base::trace_event::EstimateMemoryUsage(new_data_) +
|
| + base::trace_event::EstimateMemoryUsage(pending_write_data_) +
|
| + base::trace_event::EstimateMemoryUsage(server_id_) +
|
| + (read_buffer_ == nullptr ? 0 : read_buffer_->size()) +
|
| + (write_buffer_ == nullptr ? 0 : write_buffer_->size()) +
|
| + base::trace_event::EstimateMemoryUsage(data_);
|
| +}
|
| +
|
| +std::string DiskCacheBasedQuicServerInfo::key() const {
|
| + return "quicserverinfo:" + server_id_.ToString();
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::OnIOComplete(CacheOperationDataShim* unused,
|
| + int rv) {
|
| + DCHECK_NE(NONE, state_);
|
| + rv = DoLoop(rv);
|
| + if (rv == ERR_IO_PENDING)
|
| + return;
|
| +
|
| + base::WeakPtr<DiskCacheBasedQuicServerInfo> weak_this =
|
| + weak_factory_.GetWeakPtr();
|
| +
|
| + if (!wait_for_ready_callback_.is_null()) {
|
| + wait_for_data_end_time_ = base::TimeTicks::Now();
|
| + RecordLastFailure();
|
| + base::ResetAndReturn(&wait_for_ready_callback_).Run(rv);
|
| + }
|
| + // |wait_for_ready_callback_| could delete the object if there is an error.
|
| + // Check if |weak_this| still exists before accessing it.
|
| + if (weak_this.get() && ready_ && !pending_write_data_.empty()) {
|
| + DCHECK_EQ(NONE, state_);
|
| + PersistInternal();
|
| + }
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoLoop(int rv) {
|
| + do {
|
| + switch (state_) {
|
| + case GET_BACKEND:
|
| + rv = DoGetBackend();
|
| + break;
|
| + case GET_BACKEND_COMPLETE:
|
| + rv = DoGetBackendComplete(rv);
|
| + break;
|
| + case OPEN:
|
| + rv = DoOpen();
|
| + break;
|
| + case OPEN_COMPLETE:
|
| + rv = DoOpenComplete(rv);
|
| + break;
|
| + case READ:
|
| + rv = DoRead();
|
| + break;
|
| + case READ_COMPLETE:
|
| + rv = DoReadComplete(rv);
|
| + break;
|
| + case WAIT_FOR_DATA_READY_DONE:
|
| + rv = DoWaitForDataReadyDone();
|
| + break;
|
| + case CREATE_OR_OPEN:
|
| + rv = DoCreateOrOpen();
|
| + break;
|
| + case CREATE_OR_OPEN_COMPLETE:
|
| + rv = DoCreateOrOpenComplete(rv);
|
| + break;
|
| + case WRITE:
|
| + rv = DoWrite();
|
| + break;
|
| + case WRITE_COMPLETE:
|
| + rv = DoWriteComplete(rv);
|
| + break;
|
| + case SET_DONE:
|
| + rv = DoSetDone();
|
| + break;
|
| + default:
|
| + rv = OK;
|
| + NOTREACHED();
|
| + }
|
| + } while (rv != ERR_IO_PENDING && state_ != NONE);
|
| +
|
| + return rv;
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoGetBackendComplete(int rv) {
|
| + if (rv == OK) {
|
| + backend_ = data_shim_->backend;
|
| + state_ = OPEN;
|
| + } else {
|
| + RecordQuicServerInfoFailure(GET_BACKEND_FAILURE);
|
| + state_ = WAIT_FOR_DATA_READY_DONE;
|
| + }
|
| + return OK;
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoOpenComplete(int rv) {
|
| + if (rv == OK) {
|
| + entry_ = data_shim_->entry;
|
| + state_ = READ;
|
| + found_entry_ = true;
|
| + } else {
|
| + RecordQuicServerInfoFailure(OPEN_FAILURE);
|
| + state_ = WAIT_FOR_DATA_READY_DONE;
|
| + }
|
| +
|
| + return OK;
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoReadComplete(int rv) {
|
| + if (rv > 0)
|
| + data_.assign(read_buffer_->data(), rv);
|
| + else if (rv < 0)
|
| + RecordQuicServerInfoFailure(READ_FAILURE);
|
| +
|
| + read_buffer_ = nullptr;
|
| + state_ = WAIT_FOR_DATA_READY_DONE;
|
| + return OK;
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoWriteComplete(int rv) {
|
| + if (rv < 0)
|
| + RecordQuicServerInfoFailure(WRITE_FAILURE);
|
| + write_buffer_ = nullptr;
|
| + state_ = SET_DONE;
|
| + return OK;
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoCreateOrOpenComplete(int rv) {
|
| + if (rv != OK) {
|
| + RecordQuicServerInfoFailure(CREATE_OR_OPEN_FAILURE);
|
| + state_ = SET_DONE;
|
| + } else {
|
| + if (!entry_) {
|
| + entry_ = data_shim_->entry;
|
| + found_entry_ = true;
|
| + }
|
| + DCHECK(entry_);
|
| + state_ = WRITE;
|
| + }
|
| + return OK;
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoGetBackend() {
|
| + state_ = GET_BACKEND_COMPLETE;
|
| + return http_cache_->GetBackend(&data_shim_->backend, io_callback_);
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoOpen() {
|
| + state_ = OPEN_COMPLETE;
|
| + return backend_->OpenEntry(key(), &data_shim_->entry, io_callback_);
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoRead() {
|
| + const int32_t size = entry_->GetDataSize(0 /* index */);
|
| + if (!size) {
|
| + state_ = WAIT_FOR_DATA_READY_DONE;
|
| + return OK;
|
| + }
|
| +
|
| + read_buffer_ = new IOBufferWithSize(size);
|
| + state_ = READ_COMPLETE;
|
| + return entry_->ReadData(
|
| + 0 /* index */, 0 /* offset */, read_buffer_.get(), size, io_callback_);
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoWrite() {
|
| + write_buffer_ = new IOBufferWithSize(new_data_.size());
|
| + memcpy(write_buffer_->data(), new_data_.data(), new_data_.size());
|
| + state_ = WRITE_COMPLETE;
|
| +
|
| + return entry_->WriteData(0 /* index */,
|
| + 0 /* offset */,
|
| + write_buffer_.get(),
|
| + new_data_.size(),
|
| + io_callback_,
|
| + true /* truncate */);
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoCreateOrOpen() {
|
| + state_ = CREATE_OR_OPEN_COMPLETE;
|
| + if (entry_)
|
| + return OK;
|
| +
|
| + if (found_entry_) {
|
| + return backend_->OpenEntry(key(), &data_shim_->entry, io_callback_);
|
| + }
|
| +
|
| + return backend_->CreateEntry(key(), &data_shim_->entry, io_callback_);
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoWaitForDataReadyDone() {
|
| + DCHECK(!ready_);
|
| + state_ = NONE;
|
| + ready_ = true;
|
| + // We close the entry because, if we shutdown before ::Persist is called,
|
| + // then we might leak a cache reference, which causes a DCHECK on shutdown.
|
| + if (entry_)
|
| + entry_->Close();
|
| + entry_ = NULL;
|
| +
|
| + RecordQuicServerInfoStatus(QUIC_SERVER_INFO_PARSE);
|
| + if (!Parse(data_)) {
|
| + if (data_.empty())
|
| + RecordQuicServerInfoFailure(PARSE_NO_DATA_FAILURE);
|
| + else
|
| + RecordQuicServerInfoFailure(PARSE_FAILURE);
|
| + }
|
| +
|
| + UMA_HISTOGRAM_TIMES("Net.QuicServerInfo.DiskCacheLoadTime",
|
| + base::TimeTicks::Now() - load_start_time_);
|
| + return OK;
|
| +}
|
| +
|
| +int DiskCacheBasedQuicServerInfo::DoSetDone() {
|
| + if (entry_)
|
| + entry_->Close();
|
| + entry_ = NULL;
|
| + base::STLClearObject(&new_data_);
|
| + state_ = NONE;
|
| + return OK;
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::RecordQuicServerInfoStatus(
|
| + QuicServerInfoAPICall call) {
|
| + if (!backend_) {
|
| + UMA_HISTOGRAM_ENUMERATION("Net.QuicDiskCache.APICall.NoBackend", call,
|
| + QUIC_SERVER_INFO_NUM_OF_API_CALLS);
|
| + } else if (backend_->GetCacheType() == MEMORY_CACHE) {
|
| + UMA_HISTOGRAM_ENUMERATION("Net.QuicDiskCache.APICall.MemoryCache", call,
|
| + QUIC_SERVER_INFO_NUM_OF_API_CALLS);
|
| + } else {
|
| + UMA_HISTOGRAM_ENUMERATION("Net.QuicDiskCache.APICall.DiskCache", call,
|
| + QUIC_SERVER_INFO_NUM_OF_API_CALLS);
|
| + }
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::RecordLastFailure() {
|
| + if (last_failure_ != NO_FAILURE) {
|
| + UMA_HISTOGRAM_ENUMERATION(
|
| + "Net.QuicDiskCache.FailureReason.WaitForDataReady",
|
| + last_failure_, NUM_OF_FAILURES);
|
| + }
|
| + last_failure_ = NO_FAILURE;
|
| +}
|
| +
|
| +void DiskCacheBasedQuicServerInfo::RecordQuicServerInfoFailure(
|
| + FailureReason failure) {
|
| + last_failure_ = failure;
|
| +
|
| + if (!backend_) {
|
| + UMA_HISTOGRAM_ENUMERATION("Net.QuicDiskCache.FailureReason.NoBackend",
|
| + failure, NUM_OF_FAILURES);
|
| + } else if (backend_->GetCacheType() == MEMORY_CACHE) {
|
| + UMA_HISTOGRAM_ENUMERATION("Net.QuicDiskCache.FailureReason.MemoryCache",
|
| + failure, NUM_OF_FAILURES);
|
| + } else {
|
| + UMA_HISTOGRAM_ENUMERATION("Net.QuicDiskCache.FailureReason.DiskCache",
|
| + failure, NUM_OF_FAILURES);
|
| + }
|
| +}
|
| +
|
| +} // namespace net
|
|
|