|  | // 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.h" | 
|  | #include "base/profiler/scoped_tracker.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/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. | 
|  | } | 
|  |  | 
|  | 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::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() { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 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_; | 
|  | pending_write_data_.clear(); | 
|  | } | 
|  |  | 
|  | 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()); | 
|  | } | 
|  |  | 
|  | DiskCacheBasedQuicServerInfo::~DiskCacheBasedQuicServerInfo() { | 
|  | DCHECK(wait_for_ready_callback_.is_null()); | 
|  | if (entry_) | 
|  | entry_->Close(); | 
|  | } | 
|  |  | 
|  | std::string DiskCacheBasedQuicServerInfo::key() const { | 
|  | return "quicserverinfo:" + server_id_.ToString(); | 
|  | } | 
|  |  | 
|  | void DiskCacheBasedQuicServerInfo::OnIOComplete(CacheOperationDataShim* unused, | 
|  | int rv) { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::OnIOComplete")); | 
|  |  | 
|  | 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) { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoGetBackendComplete")); | 
|  |  | 
|  | 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) { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoOpenComplete")); | 
|  |  | 
|  | 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) { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoReadComplete")); | 
|  |  | 
|  | if (rv > 0) | 
|  | data_.assign(read_buffer_->data(), rv); | 
|  | else if (rv < 0) | 
|  | RecordQuicServerInfoFailure(READ_FAILURE); | 
|  |  | 
|  | state_ = WAIT_FOR_DATA_READY_DONE; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int DiskCacheBasedQuicServerInfo::DoWriteComplete(int rv) { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoWriteComplete")); | 
|  |  | 
|  | if (rv < 0) | 
|  | RecordQuicServerInfoFailure(WRITE_FAILURE); | 
|  | state_ = SET_DONE; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | int DiskCacheBasedQuicServerInfo::DoCreateOrOpenComplete(int rv) { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoCreateOrOpenComplete")); | 
|  |  | 
|  | 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() { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoGetBackend")); | 
|  |  | 
|  | state_ = GET_BACKEND_COMPLETE; | 
|  | return http_cache_->GetBackend(&data_shim_->backend, io_callback_); | 
|  | } | 
|  |  | 
|  | int DiskCacheBasedQuicServerInfo::DoOpen() { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoOpen")); | 
|  |  | 
|  | state_ = OPEN_COMPLETE; | 
|  | return backend_->OpenEntry(key(), &data_shim_->entry, io_callback_); | 
|  | } | 
|  |  | 
|  | int DiskCacheBasedQuicServerInfo::DoRead() { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoRead")); | 
|  |  | 
|  | const int32 size = entry_->GetDataSize(0 /* index */); | 
|  | if (!size) { | 
|  | state_ = WAIT_FOR_DATA_READY_DONE; | 
|  | return OK; | 
|  | } | 
|  |  | 
|  | read_buffer_ = new IOBuffer(size); | 
|  | state_ = READ_COMPLETE; | 
|  | return entry_->ReadData( | 
|  | 0 /* index */, 0 /* offset */, read_buffer_.get(), size, io_callback_); | 
|  | } | 
|  |  | 
|  | int DiskCacheBasedQuicServerInfo::DoWrite() { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 DiskCacheBasedQuicServerInfo::DoWrite")); | 
|  |  | 
|  | write_buffer_ = new IOBuffer(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() { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 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() { | 
|  | // TODO(vadimt): Remove ScopedTracker below once crbug.com/422516 is fixed. | 
|  | tracked_objects::ScopedTracker tracking_profile( | 
|  | FROM_HERE_WITH_EXPLICIT_FUNCTION( | 
|  | "422516 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; | 
|  | new_data_.clear(); | 
|  | 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() == net::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() == net::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 |