// 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 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