diff options
author | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-07 22:31:20 +0000 |
---|---|---|
committer | rvargas@google.com <rvargas@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-05-07 22:31:20 +0000 |
commit | 46773166fdfb7bcfecad9bb84050ba0745955708 (patch) | |
tree | f782df29fb31ccb82d1b219967d71a7def3d9e3b /net/http | |
parent | 1f4da86c4928ccdc3c6f626d52a109b2f5da21d1 (diff) | |
download | chromium_src-46773166fdfb7bcfecad9bb84050ba0745955708.zip chromium_src-46773166fdfb7bcfecad9bb84050ba0745955708.tar.gz chromium_src-46773166fdfb7bcfecad9bb84050ba0745955708.tar.bz2 |
Http Cache: Handle the asynchronous instantiation of the
backend. We queue all trasnactions that reach Start before
we are done creating the disk cache.
BUG=26729
TEST=unittests.
Review URL: http://codereview.chromium.org/2002002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@46745 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/http')
-rw-r--r-- | net/http/http_cache.cc | 345 | ||||
-rw-r--r-- | net/http/http_cache.h | 53 | ||||
-rw-r--r-- | net/http/http_cache_transaction.cc | 113 | ||||
-rw-r--r-- | net/http/http_cache_transaction.h | 12 | ||||
-rw-r--r-- | net/http/http_cache_unittest.cc | 292 |
5 files changed, 624 insertions, 191 deletions
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc index fc7905b..08b9aa6 100644 --- a/net/http/http_cache.cc +++ b/net/http/http_cache.cc @@ -17,6 +17,7 @@ #include "base/message_loop.h" #include "base/pickle.h" #include "base/ref_counted.h" +#include "base/stl_util-inl.h" #include "base/string_util.h" #include "net/base/io_buffer.h" #include "net/base/load_flags.h" @@ -49,13 +50,14 @@ HttpCache::ActiveEntry::~ActiveEntry() { //----------------------------------------------------------------------------- // This structure keeps track of work items that are attempting to create or -// open cache entries. -struct HttpCache::NewEntry { - NewEntry() : disk_entry(NULL), writer(NULL) {} - ~NewEntry() {} +// open cache entries or the backend itself. +struct HttpCache::PendingOp { + PendingOp() : disk_entry(NULL), writer(NULL), callback(NULL) {} + ~PendingOp() {} disk_cache::Entry* disk_entry; WorkItem* writer; + CompletionCallback* callback; // BackendCallback. WorkItemList pending_queue; }; @@ -63,19 +65,24 @@ struct HttpCache::NewEntry { // The type of operation represented by a work item. enum WorkItemOperation { + WI_CREATE_BACKEND, WI_OPEN_ENTRY, WI_CREATE_ENTRY, WI_DOOM_ENTRY }; -// A work item encapsulates a single request for cache entry with all the +// A work item encapsulates a single request to the backend with all the // information needed to complete that request. class HttpCache::WorkItem { public: - WorkItem(ActiveEntry** entry, Transaction* trans, WorkItemOperation operation) - : entry_(entry), trans_(trans), operation_(operation) {} + WorkItem(WorkItemOperation operation, Transaction* trans, ActiveEntry** entry) + : operation_(operation), trans_(trans), entry_(entry), callback_(NULL), + backend_(NULL) {} + WorkItem(WorkItemOperation operation, Transaction* trans, + CompletionCallback* cb, disk_cache::Backend** backend) + : operation_(operation), trans_(trans), entry_(NULL), callback_(cb), + backend_(backend) {} ~WorkItem() {} - WorkItemOperation operation() { return operation_; } // Calls back the transaction with the result of the operation. void NotifyTransaction(int result, ActiveEntry* entry) { @@ -85,15 +92,27 @@ class HttpCache::WorkItem { trans_->io_callback()->Run(result); } + // Notifies the caller about the operation completion. + void DoCallback(int result, disk_cache::Backend* backend) { + if (backend_) + *backend_ = backend; + if (callback_) + callback_->Run(result); + } + + WorkItemOperation operation() { return operation_; } void ClearTransaction() { trans_ = NULL; } void ClearEntry() { entry_ = NULL; } + void ClearCallback() { callback_ = NULL; } bool Matches(Transaction* trans) const { return trans == trans_; } - bool IsValid() const { return trans_ || entry_; } + bool IsValid() const { return trans_ || entry_ || callback_; } private: - ActiveEntry** entry_; - Transaction* trans_; WorkItemOperation operation_; + Transaction* trans_; + ActiveEntry** entry_; + CompletionCallback* callback_; // User callback. + disk_cache::Backend** backend_; }; //----------------------------------------------------------------------------- @@ -102,18 +121,18 @@ class HttpCache::WorkItem { // pass multiple arguments to the completion routine. class HttpCache::BackendCallback : public CallbackRunner<Tuple1<int> > { public: - BackendCallback(HttpCache* cache, NewEntry* entry) - : cache_(cache), entry_(entry) {} + BackendCallback(HttpCache* cache, PendingOp* pending_op) + : cache_(cache), pending_op_(pending_op) {} ~BackendCallback() {} virtual void RunWithParams(const Tuple1<int>& params) { - cache_->OnIOComplete(params.a, entry_); + cache_->OnIOComplete(params.a, pending_op_); delete this; } private: HttpCache* cache_; - NewEntry* entry_; + PendingOp* pending_op_; DISALLOW_COPY_AND_ASSIGN(BackendCallback); }; @@ -205,6 +224,8 @@ HttpCache::HttpCache(NetworkChangeNotifier* network_change_notifier, int cache_size) : disk_cache_dir_(cache_dir), cache_thread_(cache_thread), + temp_backend_(NULL), + building_backend_(false), mode_(NORMAL), type_(DISK_CACHE), network_layer_(HttpNetworkLayer::CreateFactory( @@ -212,7 +233,8 @@ HttpCache::HttpCache(NetworkChangeNotifier* network_change_notifier, ssl_config_service, http_auth_handler_factory)), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), enable_range_support_(true), - cache_size_(cache_size) { + cache_size_(cache_size), + create_backend_fn_(NULL) { } HttpCache::HttpCache(HttpNetworkSession* session, @@ -221,12 +243,15 @@ HttpCache::HttpCache(HttpNetworkSession* session, int cache_size) : disk_cache_dir_(cache_dir), cache_thread_(cache_thread), + temp_backend_(NULL), + building_backend_(false), mode_(NORMAL), type_(DISK_CACHE), network_layer_(HttpNetworkLayer::CreateFactory(session)), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), enable_range_support_(true), - cache_size_(cache_size) { + cache_size_(cache_size), + create_backend_fn_(NULL) { } HttpCache::HttpCache(NetworkChangeNotifier* network_change_notifier, @@ -235,25 +260,33 @@ HttpCache::HttpCache(NetworkChangeNotifier* network_change_notifier, SSLConfigService* ssl_config_service, HttpAuthHandlerFactory* http_auth_handler_factory, int cache_size) - : mode_(NORMAL), + : cache_thread_(NULL), + temp_backend_(NULL), + building_backend_(false), + mode_(NORMAL), type_(MEMORY_CACHE), network_layer_(HttpNetworkLayer::CreateFactory( network_change_notifier, host_resolver, proxy_service, ssl_config_service, http_auth_handler_factory)), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), enable_range_support_(true), - cache_size_(cache_size) { + cache_size_(cache_size), + create_backend_fn_(NULL) { } HttpCache::HttpCache(HttpTransactionFactory* network_layer, disk_cache::Backend* disk_cache) - : mode_(NORMAL), + : cache_thread_(NULL), + temp_backend_(NULL), + building_backend_(false), + mode_(NORMAL), type_(DISK_CACHE), network_layer_(network_layer), disk_cache_(disk_cache), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), enable_range_support_(true), - cache_size_(0) { + cache_size_(0), + create_backend_fn_(NULL) { } HttpCache::~HttpCache() { @@ -270,9 +303,19 @@ HttpCache::~HttpCache() { DeactivateEntry(entry); } - ActiveEntriesSet::iterator it = doomed_entries_.begin(); - for (; it != doomed_entries_.end(); ++it) - delete *it; + STLDeleteElements(&doomed_entries_); + + PendingOpsMap::iterator pending_it = pending_ops_.begin(); + for (; pending_it != pending_ops_.end(); ++pending_it) { + // We are not notifying the transactions about the cache going away, even + // though they are waiting for a callback that will never fire. + PendingOp* pending_op = pending_it->second; + delete pending_op->writer; + delete pending_op->callback; + + STLDeleteElements(&pending_op->pending_queue); + delete pending_op; + } } disk_cache::Backend* HttpCache::GetBackend() { @@ -296,13 +339,21 @@ disk_cache::Backend* HttpCache::GetBackend() { int HttpCache::GetBackend(disk_cache::Backend** backend, CompletionCallback* callback) { DCHECK(callback != NULL); - *backend = GetBackend(); - return OK; + + if (disk_cache_.get()) { + *backend = disk_cache_.get(); + return OK; + } + + DCHECK_GE(cache_size_, 0); + return CreateBackend(backend, callback); } int HttpCache::CreateTransaction(scoped_ptr<HttpTransaction>* trans) { // Do lazy initialization of disk cache if needed. - GetBackend(); + if (!disk_cache_.get()) + CreateBackend(NULL, NULL); // We don't care about the result. + trans->reset(new HttpCache::Transaction(this, enable_range_support_)); return OK; } @@ -392,6 +443,68 @@ void HttpCache::CloseCurrentConnections() { //----------------------------------------------------------------------------- +int HttpCache::CreateBackend(disk_cache::Backend** backend, + CompletionCallback* callback) { + // We may end up with no folder name and no cache if the initialization + // of the disk cache fails. + if (type_ != MEMORY_CACHE && disk_cache_dir_.empty()) + return ERR_FAILED; + + DCHECK_GE(cache_size_, 0); + building_backend_ = true; + + scoped_ptr<WorkItem> item(new WorkItem(WI_CREATE_BACKEND, NULL, callback, + backend)); + + // This is the only operation that we can do that is not related to any given + // entry, so we use an empty key for it. + PendingOp* pending_op = GetPendingOp(""); + if (pending_op->writer) { + if (callback) + pending_op->pending_queue.push_back(item.release()); + return ERR_IO_PENDING; + } + + DCHECK(pending_op->pending_queue.empty()); + + pending_op->writer = item.release(); + BackendCallback* my_callback = new BackendCallback(this, pending_op); + pending_op->callback = my_callback; + + // See if this is a unit test. TODO(rvargas): Cleanup this after removing the + // overloaded method. + int rv; + if (create_backend_fn_) { + rv = create_backend_fn_(type_, disk_cache_dir_, cache_size_, true, + cache_thread_, &temp_backend_, my_callback); + } else { + rv = disk_cache::CreateCacheBackend(type_, disk_cache_dir_, cache_size_, + true, cache_thread_, &temp_backend_, + my_callback); + } + + if (rv != ERR_IO_PENDING) { + pending_op->writer->ClearCallback(); + my_callback->Run(rv); + } + + return rv; +} + +int HttpCache::GetBackendForTransaction(Transaction* trans) { + if (disk_cache_.get()) + return OK; + + if (!building_backend_) + return ERR_FAILED; + + WorkItem* item = new WorkItem(WI_CREATE_BACKEND, trans, NULL, NULL); + PendingOp* pending_op = GetPendingOp(""); + DCHECK(pending_op->writer); + pending_op->pending_queue.push_back(item); + return ERR_IO_PENDING; +} + // Generate a key that can be used inside the cache. std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) { // Strip out the reference, username, and password sections of the URL. @@ -457,17 +570,18 @@ int HttpCache::DoomEntry(const std::string& key, Transaction* trans) { int HttpCache::AsyncDoomEntry(const std::string& key, Transaction* trans) { DCHECK(trans); - WorkItem* item = new WorkItem(NULL, trans, WI_DOOM_ENTRY); - NewEntry* new_entry = GetNewEntry(key); - if (new_entry->writer) { - new_entry->pending_queue.push_back(item); + WorkItem* item = new WorkItem(WI_DOOM_ENTRY, trans, NULL); + PendingOp* pending_op = GetPendingOp(key); + if (pending_op->writer) { + pending_op->pending_queue.push_back(item); return ERR_IO_PENDING; } - DCHECK(new_entry->pending_queue.empty()); + DCHECK(pending_op->pending_queue.empty()); - new_entry->writer = item; - BackendCallback* my_callback = new BackendCallback(this, new_entry); + pending_op->writer = item; + BackendCallback* my_callback = new BackendCallback(this, pending_op); + pending_op->callback = my_callback; int rv = disk_cache_->DoomEntry(key, my_callback); if (rv != ERR_IO_PENDING) { @@ -536,38 +650,39 @@ void HttpCache::SlowDeactivateEntry(ActiveEntry* entry) { } } -HttpCache::NewEntry* HttpCache::GetNewEntry(const std::string& key) { +HttpCache::PendingOp* HttpCache::GetPendingOp(const std::string& key) { DCHECK(!FindActiveEntry(key)); - NewEntriesMap::const_iterator it = new_entries_.find(key); - if (it != new_entries_.end()) + PendingOpsMap::const_iterator it = pending_ops_.find(key); + if (it != pending_ops_.end()) return it->second; - NewEntry* entry = new NewEntry(); - new_entries_[key] = entry; - return entry; + PendingOp* operation = new PendingOp(); + pending_ops_[key] = operation; + return operation; } -void HttpCache::DeleteNewEntry(NewEntry* entry) { +void HttpCache::DeletePendingOp(PendingOp* pending_op) { std::string key; - if (entry->disk_entry) - key = entry->disk_entry->GetKey(); + if (pending_op->disk_entry) + key = pending_op->disk_entry->GetKey(); if (!key.empty()) { - NewEntriesMap::iterator it = new_entries_.find(key); - DCHECK(it != new_entries_.end()); - new_entries_.erase(it); + PendingOpsMap::iterator it = pending_ops_.find(key); + DCHECK(it != pending_ops_.end()); + pending_ops_.erase(it); } else { - for (NewEntriesMap::iterator it = new_entries_.begin(); - it != new_entries_.end(); ++it) { - if (it->second == entry) { - new_entries_.erase(it); + for (PendingOpsMap::iterator it = pending_ops_.begin(); + it != pending_ops_.end(); ++it) { + if (it->second == pending_op) { + pending_ops_.erase(it); break; } } } + DCHECK(pending_op->pending_queue.empty()); - delete entry; + delete pending_op; } int HttpCache::OpenEntry(const std::string& key, ActiveEntry** entry, @@ -578,19 +693,20 @@ int HttpCache::OpenEntry(const std::string& key, ActiveEntry** entry, return OK; } - WorkItem* item = new WorkItem(entry, trans, WI_OPEN_ENTRY); - NewEntry* new_entry = GetNewEntry(key); - if (new_entry->writer) { - new_entry->pending_queue.push_back(item); + WorkItem* item = new WorkItem(WI_OPEN_ENTRY, trans, entry); + PendingOp* pending_op = GetPendingOp(key); + if (pending_op->writer) { + pending_op->pending_queue.push_back(item); return ERR_IO_PENDING; } - DCHECK(new_entry->pending_queue.empty()); + DCHECK(pending_op->pending_queue.empty()); - new_entry->writer = item; - BackendCallback* my_callback = new BackendCallback(this, new_entry); + pending_op->writer = item; + BackendCallback* my_callback = new BackendCallback(this, pending_op); + pending_op->callback = my_callback; - int rv = disk_cache_->OpenEntry(key, &(new_entry->disk_entry), my_callback); + int rv = disk_cache_->OpenEntry(key, &(pending_op->disk_entry), my_callback); if (rv != ERR_IO_PENDING) { item->ClearTransaction(); my_callback->Run(rv); @@ -603,19 +719,21 @@ int HttpCache::CreateEntry(const std::string& key, ActiveEntry** entry, Transaction* trans) { DCHECK(!FindActiveEntry(key)); - WorkItem* item = new WorkItem(entry, trans, WI_CREATE_ENTRY); - NewEntry* new_entry = GetNewEntry(key); - if (new_entry->writer) { - new_entry->pending_queue.push_back(item); + WorkItem* item = new WorkItem(WI_CREATE_ENTRY, trans, entry); + PendingOp* pending_op = GetPendingOp(key); + if (pending_op->writer) { + pending_op->pending_queue.push_back(item); return ERR_IO_PENDING; } - DCHECK(new_entry->pending_queue.empty()); + DCHECK(pending_op->pending_queue.empty()); - new_entry->writer = item; - BackendCallback* my_callback = new BackendCallback(this, new_entry); + pending_op->writer = item; + BackendCallback* my_callback = new BackendCallback(this, pending_op); + pending_op->callback = my_callback; - int rv = disk_cache_->CreateEntry(key, &(new_entry->disk_entry), my_callback); + int rv = disk_cache_->CreateEntry(key, &(pending_op->disk_entry), + my_callback); if (rv != ERR_IO_PENDING) { item->ClearTransaction(); my_callback->Run(rv); @@ -755,9 +873,21 @@ void HttpCache::RemovePendingTransaction(Transaction* trans) { if (found) return; - NewEntriesMap::const_iterator j = new_entries_.find(trans->key()); - if (j != new_entries_.end()) - found = RemovePendingTransactionFromNewEntry(j->second, trans); + if (building_backend_) { + PendingOpsMap::const_iterator j = pending_ops_.find(""); + if (j != pending_ops_.end()) + found = RemovePendingTransactionFromPendingOp(j->second, trans); + + if (found) + return; + } + + PendingOpsMap::const_iterator j = pending_ops_.find(trans->key()); + if (j != pending_ops_.end()) + found = RemovePendingTransactionFromPendingOp(j->second, trans); + + if (found) + return; ActiveEntriesSet::iterator k = doomed_entries_.begin(); for (; k != doomed_entries_.end() && !found; ++k) @@ -779,14 +909,14 @@ bool HttpCache::RemovePendingTransactionFromEntry(ActiveEntry* entry, return true; } -bool HttpCache::RemovePendingTransactionFromNewEntry(NewEntry* entry, - Transaction* trans) { - if (entry->writer->Matches(trans)) { - entry->writer->ClearTransaction(); - entry->writer->ClearEntry(); +bool HttpCache::RemovePendingTransactionFromPendingOp(PendingOp* pending_op, + Transaction* trans) { + if (pending_op->writer->Matches(trans)) { + pending_op->writer->ClearTransaction(); + pending_op->writer->ClearEntry(); return true; } - WorkItemList& pending_queue = entry->pending_queue; + WorkItemList& pending_queue = pending_op->pending_queue; WorkItemList::iterator it = pending_queue.begin(); for (; it != pending_queue.end(); ++it) { @@ -837,9 +967,14 @@ void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) { } } -void HttpCache::OnIOComplete(int result, NewEntry* new_entry) { - scoped_ptr<WorkItem> item(new_entry->writer); - WorkItemOperation op = item->operation(); +void HttpCache::OnIOComplete(int result, PendingOp* pending_op) { + WorkItemOperation op = pending_op->writer->operation(); + + // Completing the creation of the backend is simpler than the other cases. + if (op == WI_CREATE_BACKEND) + return OnBackendCreated(result, pending_op); + + scoped_ptr<WorkItem> item(pending_op->writer); bool fail_requests = false; ActiveEntry* entry = NULL; @@ -849,31 +984,31 @@ void HttpCache::OnIOComplete(int result, NewEntry* new_entry) { // Anything after a Doom has to be restarted. fail_requests = true; } else if (item->IsValid()) { - key = new_entry->disk_entry->GetKey(); - entry = ActivateEntry(key, new_entry->disk_entry); + key = pending_op->disk_entry->GetKey(); + entry = ActivateEntry(key, pending_op->disk_entry); } else { // The writer transaction is gone. if (op == WI_CREATE_ENTRY) - new_entry->disk_entry->Doom(); - new_entry->disk_entry->Close(); + pending_op->disk_entry->Doom(); + pending_op->disk_entry->Close(); fail_requests = true; } } // We are about to notify a bunch of transactions, and they may decide to - // re-issue a request (or send a different one). If we don't delete new_entry, - // the new request will be appended to the end of the list, and we'll see it - // again from this point before it has a chance to complete (and we'll be - // messing out the request order). The down side is that if for some reason - // notifying request A ends up cancelling request B (for the same key), we - // won't find request B anywhere (because it would be in a local variable + // re-issue a request (or send a different one). If we don't delete + // pending_op, the new request will be appended to the end of the list, and + // we'll see it again from this point before it has a chance to complete (and + // we'll be messing out the request order). The down side is that if for some + // reason notifying request A ends up cancelling request B (for the same key), + // we won't find request B anywhere (because it would be in a local variable // here) and that's bad. If there is a chance for that to happen, we'll have // to move the callback used to be a CancelableCallback. By the way, for this // to happen the action (to cancel B) has to be synchronous to the // notification for request A. WorkItemList pending_items; - pending_items.swap(new_entry->pending_queue); - DeleteNewEntry(new_entry); + pending_items.swap(pending_op->pending_queue); + DeletePendingOp(pending_op); item->NotifyTransaction(result, entry); @@ -920,4 +1055,32 @@ void HttpCache::OnIOComplete(int result, NewEntry* new_entry) { } } +void HttpCache::OnBackendCreated(int result, PendingOp* pending_op) { + scoped_ptr<WorkItem> item(pending_op->writer); + WorkItemOperation op = item->operation(); + DCHECK_EQ(WI_CREATE_BACKEND, op); + + if (type_ != MEMORY_CACHE) + disk_cache_dir_ = FilePath(); // Reclaim memory. + + if (result == OK) + disk_cache_.reset(temp_backend_); + + item->DoCallback(result, temp_backend_); + + // Notify and all callers and delete all pending work items. + while (!pending_op->pending_queue.empty()) { + scoped_ptr<WorkItem> pending_item(pending_op->pending_queue.front()); + pending_op->pending_queue.pop_front(); + DCHECK_EQ(WI_CREATE_BACKEND, pending_item->operation()); + + // This could be an external caller or a transaction waiting on Start(). + pending_item->DoCallback(result, temp_backend_); + pending_item->NotifyTransaction(result, NULL); + } + + DeletePendingOp(pending_op); + building_backend_ = false; +} + } // namespace net diff --git a/net/http/http_cache.h b/net/http/http_cache.h index 15561fb..472c272 100644 --- a/net/http/http_cache.h +++ b/net/http/http_cache.h @@ -26,6 +26,7 @@ #include "net/base/cache_type.h" #include "net/base/completion_callback.h" #include "net/http/http_transaction_factory.h" +#include "testing/gtest/include/gtest/gtest_prod.h" class GURL; class MessageLoop; @@ -184,6 +185,8 @@ class HttpCache : public HttpTransactionFactory, friend class ::ViewCacheHelper; private: + FRIEND_TEST(HttpCacheTest, SimpleGET_WaitForBackend); + FRIEND_TEST(HttpCacheTest, SimpleGET_WaitForBackend_CancelCreate); // Types -------------------------------------------------------------------- @@ -192,7 +195,7 @@ class HttpCache : public HttpTransactionFactory, class Transaction; class WorkItem; friend class Transaction; - struct NewEntry; // Info for an entry under construction. + struct PendingOp; // Info for an entry under construction. typedef std::list<Transaction*> TransactionList; typedef std::list<WorkItem*> WorkItemList; @@ -210,11 +213,27 @@ class HttpCache : public HttpTransactionFactory, }; typedef base::hash_map<std::string, ActiveEntry*> ActiveEntriesMap; - typedef base::hash_map<std::string, NewEntry*> NewEntriesMap; + typedef base::hash_map<std::string, PendingOp*> PendingOpsMap; typedef std::set<ActiveEntry*> ActiveEntriesSet; + typedef int (*CreateCacheBackendFn)(CacheType, const FilePath&, int, + bool, MessageLoop*, disk_cache::Backend**, + CompletionCallback*); + // Methods ------------------------------------------------------------------ + // Creates the |backend| object and notifies the |callback| when the operation + // completes. Returns an error code. + int CreateBackend(disk_cache::Backend** backend, + CompletionCallback* callback); + + // Makes sure that the backend creation is complete before allowing the + // provided transaction to use the object. Returns an error code. |trans| + // will be notified via its IO callback if this method returns ERR_IO_PENDING. + // The transaction is free to use the backend directly at any time after + // receiving the notification. + int GetBackendForTransaction(Transaction* trans); + // Generates the cache key for this request. std::string GenerateCacheKey(const HttpRequestInfo*); @@ -246,12 +265,12 @@ class HttpCache : public HttpTransactionFactory, // Deletes an ActiveEntry using an exhaustive search. void SlowDeactivateEntry(ActiveEntry* entry); - // Returns the NewEntry for the desired |key|. If an entry is not under - // construction already, a new NewEntry structure is created. - NewEntry* GetNewEntry(const std::string& key); + // Returns the PendingOp for the desired |key|. If an entry is not under + // construction already, a new PendingOp structure is created. + PendingOp* GetPendingOp(const std::string& key); - // Deletes a NewEntry. - void DeleteNewEntry(NewEntry* entry); + // Deletes a PendingOp. + void DeletePendingOp(PendingOp* pending_op); // Opens the disk cache entry associated with |key|, returning an ActiveEntry // in |*entry|. |trans| will be notified via its IO callback if this method @@ -292,16 +311,16 @@ class HttpCache : public HttpTransactionFactory, void ConvertWriterToReader(ActiveEntry* entry); // Removes the transaction |trans|, from the pending list of an entry - // (NewEntry, active or doomed entry). + // (PendingOp, active or doomed entry). void RemovePendingTransaction(Transaction* trans); // Removes the transaction |trans|, from the pending list of |entry|. bool RemovePendingTransactionFromEntry(ActiveEntry* entry, Transaction* trans); - // Removes the transaction |trans|, from the pending list of |entry|. - bool RemovePendingTransactionFromNewEntry(NewEntry* entry, - Transaction* trans); + // Removes the transaction |trans|, from the pending list of |pending_op|. + bool RemovePendingTransactionFromPendingOp(PendingOp* pending_op, + Transaction* trans); // Resumes processing the pending list of |entry|. void ProcessPendingQueue(ActiveEntry* entry); @@ -313,7 +332,10 @@ class HttpCache : public HttpTransactionFactory, // Callbacks ---------------------------------------------------------------- // Processes BackendCallback notifications. - void OnIOComplete(int result, NewEntry* entry); + void OnIOComplete(int result, PendingOp* entry); + + // Processes the backend creation notification. + void OnBackendCreated(int result, PendingOp* pending_op); // Variables ---------------------------------------------------------------- @@ -321,6 +343,8 @@ class HttpCache : public HttpTransactionFactory, // Used when lazily constructing the disk_cache_. FilePath disk_cache_dir_; MessageLoop* cache_thread_; + disk_cache::Backend* temp_backend_; + bool building_backend_; Mode mode_; CacheType type_; @@ -335,7 +359,7 @@ class HttpCache : public HttpTransactionFactory, ActiveEntriesSet doomed_entries_; // The set of entries "under construction". - NewEntriesMap new_entries_; + PendingOpsMap pending_ops_; ScopedRunnableMethodFactory<HttpCache> task_factory_; @@ -345,6 +369,9 @@ class HttpCache : public HttpTransactionFactory, typedef base::hash_map<std::string, int> PlaybackCacheMap; scoped_ptr<PlaybackCacheMap> playback_cache_map_; + // Used for unit tests. + CreateCacheBackendFn create_backend_fn_; + DISALLOW_COPY_AND_ASSIGN(HttpCache); }; diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc index 8beb825..ffb5748 100644 --- a/net/http/http_cache_transaction.cc +++ b/net/http/http_cache_transaction.cc @@ -172,44 +172,9 @@ int HttpCache::Transaction::Start(const HttpRequestInfo* request, SetRequest(net_log, request); - int rv; - - if (!ShouldPassThrough()) { - cache_key_ = cache_->GenerateCacheKey(request); - - // Requested cache access mode. - if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) { - mode_ = READ; - } else if (effective_load_flags_ & LOAD_BYPASS_CACHE) { - mode_ = WRITE; - } else { - mode_ = READ_WRITE; - } - - // Downgrade to UPDATE if the request has been externally conditionalized. - if (external_validation_.initialized) { - if (mode_ & WRITE) { - // Strip off the READ_DATA bit (and maybe add back a READ_META bit - // in case READ was off). - mode_ = UPDATE; - } else { - mode_ = NONE; - } - } - } - - // If must use cache, then we must fail. This can happen for back/forward - // navigations to a page generated via a form post. - if (!(mode_ & READ) && effective_load_flags_ & LOAD_ONLY_FROM_CACHE) - return ERR_CACHE_MISS; - - if (mode_ == NONE) { - if (partial_.get()) - partial_->RestoreHeaders(&custom_request_->extra_headers); - rv = BeginNetworkRequest(); - } else { - rv = AddToEntry(); - } + // We have to wait until the backend is initialized so we start the SM. + next_state_ = STATE_GET_BACKEND; + int rv = DoLoop(OK); // Setting this here allows us to check for the existance of a callback_ to // determine if we are still inside Start. @@ -428,6 +393,13 @@ int HttpCache::Transaction::DoLoop(int result) { State state = next_state_; next_state_ = STATE_NONE; switch (state) { + case STATE_GET_BACKEND: + DCHECK_EQ(OK, rv); + rv = DoGetBackend(); + break; + case STATE_GET_BACKEND_COMPLETE: + rv = DoGetBackendComplete(rv); + break; case STATE_SEND_REQUEST: DCHECK_EQ(OK, rv); rv = DoSendRequest(); @@ -562,6 +534,58 @@ int HttpCache::Transaction::DoLoop(int result) { return rv; } +int HttpCache::Transaction::DoGetBackend() { + cache_pending_ = true; + next_state_ = STATE_GET_BACKEND_COMPLETE; + net_log_.BeginEvent(NetLog::TYPE_HTTP_CACHE_WAITING, NULL); + return cache_->GetBackendForTransaction(this); +} + +int HttpCache::Transaction::DoGetBackendComplete(int result) { + DCHECK(result == OK || result == ERR_FAILED); + net_log_.EndEvent(NetLog::TYPE_HTTP_CACHE_WAITING, NULL); + cache_pending_ = false; + + if (!ShouldPassThrough()) { + cache_key_ = cache_->GenerateCacheKey(request_); + + // Requested cache access mode. + if (effective_load_flags_ & LOAD_ONLY_FROM_CACHE) { + mode_ = READ; + } else if (effective_load_flags_ & LOAD_BYPASS_CACHE) { + mode_ = WRITE; + } else { + mode_ = READ_WRITE; + } + + // Downgrade to UPDATE if the request has been externally conditionalized. + if (external_validation_.initialized) { + if (mode_ & WRITE) { + // Strip off the READ_DATA bit (and maybe add back a READ_META bit + // in case READ was off). + mode_ = UPDATE; + } else { + mode_ = NONE; + } + } + } + + // If must use cache, then we must fail. This can happen for back/forward + // navigations to a page generated via a form post. + if (!(mode_ & READ) && effective_load_flags_ & LOAD_ONLY_FROM_CACHE) + return ERR_CACHE_MISS; + + if (mode_ == NONE) { + if (partial_.get()) + partial_->RestoreHeaders(&custom_request_->extra_headers); + next_state_ = STATE_SEND_REQUEST; + } else { + next_state_ = STATE_INIT_ENTRY; + } + + return OK; +} + int HttpCache::Transaction::DoSendRequest() { DCHECK(mode_ & WRITE || mode_ == NONE); DCHECK(!network_trans_.get()); @@ -902,7 +926,7 @@ int HttpCache::Transaction::DoOverwriteCachedResponse() { response_ = *new_response_; target_state_ = STATE_TRUNCATE_CACHED_DATA; next_state_ = truncated_ ? STATE_CACHE_WRITE_TRUNCATED_RESPONSE : - STATE_CACHE_WRITE_RESPONSE; + STATE_CACHE_WRITE_RESPONSE; return OK; } @@ -1268,12 +1292,6 @@ bool HttpCache::Transaction::ShouldPassThrough() { return true; } -int HttpCache::Transaction::AddToEntry() { - next_state_ = STATE_INIT_ENTRY; - cache_pending_ = false; - return DoLoop(OK); -} - int HttpCache::Transaction::BeginCacheRead() { // We don't support any combination of LOAD_ONLY_FROM_CACHE and byte ranges. if (response_.headers->response_code() == 206 || partial_.get()) { @@ -1388,11 +1406,6 @@ int HttpCache::Transaction::BeginExternallyConditionalizedRequest() { return OK; } -int HttpCache::Transaction::BeginNetworkRequest() { - next_state_ = STATE_SEND_REQUEST; - return DoLoop(OK); -} - int HttpCache::Transaction::RestartNetworkRequest() { DCHECK(mode_ & WRITE || mode_ == NONE); DCHECK(network_trans_.get()); diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h index 9d13b11..4d16a25 100644 --- a/net/http/http_cache_transaction.h +++ b/net/http/http_cache_transaction.h @@ -111,6 +111,8 @@ class HttpCache::Transaction : public HttpTransaction { enum State { STATE_NONE, + STATE_GET_BACKEND, + STATE_GET_BACKEND_COMPLETE, STATE_SEND_REQUEST, STATE_SEND_REQUEST_COMPLETE, STATE_SUCCESSFUL_SEND_REQUEST, @@ -160,6 +162,8 @@ class HttpCache::Transaction : public HttpTransaction { // Each of these methods corresponds to a State value. If there is an // argument, the value corresponds to the return of the previous state or // corresponding callback. + int DoGetBackend(); + int DoGetBackendComplete(int result); int DoSendRequest(); int DoSendRequestComplete(int result); int DoSuccessfulSendRequest(); @@ -202,9 +206,6 @@ class HttpCache::Transaction : public HttpTransaction { // layer (skipping the cache entirely). bool ShouldPassThrough(); - // Associates this transaction with a cache entry. - int AddToEntry(); - // Called to begin reading from the cache. Returns network error code. int BeginCacheRead(); @@ -225,9 +226,6 @@ class HttpCache::Transaction : public HttpTransaction { // Returns a network error code. int BeginExternallyConditionalizedRequest(); - // Called to begin a network transaction. Returns network error code. - int BeginNetworkRequest(); - // Called to restart a network transaction after an error. Returns network // error code. int RestartNetworkRequest(); @@ -332,7 +330,7 @@ class HttpCache::Transaction : public HttpTransaction { CompletionCallbackImpl<Transaction> io_callback_; scoped_refptr<CancelableCompletionCallback<Transaction> > cache_callback_; scoped_refptr<CancelableCompletionCallback<Transaction> > - write_headers_callback_; + write_headers_callback_; }; } // namespace net diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc index 32b3343..cf12dfc 100644 --- a/net/http/http_cache_unittest.cc +++ b/net/http/http_cache_unittest.cc @@ -570,6 +570,13 @@ class MockHttpCache { net::HttpCache http_cache_; }; +// This version of the disk cache doesn't invoke CreateEntry callbacks. +class MockDiskCacheNoCB : public MockDiskCache { + virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry, + net::CompletionCallback* callback) { + return net::ERR_IO_PENDING; + } +}; //----------------------------------------------------------------------------- // helpers @@ -869,6 +876,168 @@ struct Context { //----------------------------------------------------------------------------- // tests +namespace net { + +// This is a friend class of the HttpCache so that we can interact with it to +// perform a few internal tests. We override the backend factory method so that +// we can test the logic of backend instantiation. +class HttpCacheTest : public testing::Test { + public: + // Controls whether to block the backend instantiation or not. If |block| is + // true, any blocked call will be notified via the provided callback. + void BlockCacheCreation(bool block) { + block_ = block; + if (!block_ && callback_) { + *backend_ = new MockDiskCache(); + callback_->Run(OK); + callback_ = NULL; + } + } + + protected: + virtual void SetUp() { + current_test_ = this; + backend_ = NULL; + callback_ = NULL; + block_ = false; + } + + virtual void TearDown() { current_test_ = NULL; } + + // Implements disk_cache::CreateCacheBackend(). + static int MockCreateBackend(CacheType type, const FilePath& path, + int max_bytes, bool force, MessageLoop* thread, + disk_cache::Backend** backend, + CompletionCallback* callback) { + if (current_test_ == NULL) { + EXPECT_TRUE(current_test_); + return ERR_FAILED; + } + if (!current_test_->block_) { + *backend = new MockDiskCache(); + return OK; + } + + current_test_->backend_ = backend; + current_test_->callback_ = callback; + return ERR_IO_PENDING; + } + + private: + static HttpCacheTest* current_test_; + disk_cache::Backend** backend_; + CompletionCallback* callback_; + bool block_; +}; + +// Static. +HttpCacheTest* HttpCacheTest::current_test_ = NULL; + +// Tests that we queue requests when initializing the backend. +TEST_F(HttpCacheTest, SimpleGET_WaitForBackend) { + MockHttpCache cache(NULL); + cache.http_cache()->create_backend_fn_ = MockCreateBackend; + cache.http_cache()->set_type(MEMORY_CACHE); + BlockCacheCreation(true); + + MockHttpRequest request0(kSimpleGET_Transaction); + MockHttpRequest request1(kTypicalGET_Transaction); + MockHttpRequest request2(kETagGET_Transaction); + + std::vector<Context*> context_list; + const int kNumTransactions = 3; + + for (int i = 0; i < kNumTransactions; i++) { + context_list.push_back(new Context()); + Context* c = context_list[i]; + + c->result = cache.http_cache()->CreateTransaction(&c->trans); + EXPECT_EQ(OK, c->result); + } + + context_list[0]->result = context_list[0]->trans->Start( + &request0, &context_list[0]->callback, BoundNetLog()); + context_list[1]->result = context_list[1]->trans->Start( + &request1, &context_list[1]->callback, BoundNetLog()); + context_list[2]->result = context_list[2]->trans->Start( + &request2, &context_list[2]->callback, BoundNetLog()); + + // Just to make sure that everything is still pending. + MessageLoop::current()->RunAllPending(); + + // The first request should be creating the disk cache. + EXPECT_FALSE(context_list[0]->callback.have_result()); + + BlockCacheCreation(false); + + MessageLoop::current()->RunAllPending(); + EXPECT_EQ(3, cache.network_layer()->transaction_count()); + EXPECT_EQ(3, cache.disk_cache()->create_count()); + + for (int i = 0; i < kNumTransactions; ++i) { + EXPECT_TRUE(context_list[i]->callback.have_result()); + delete context_list[i]; + } +} + +// Tests that we can cancel requests that are queued waiting for the backend +// to be initialized. +TEST_F(HttpCacheTest, SimpleGET_WaitForBackend_CancelCreate) { + MockHttpCache cache(NULL); + cache.http_cache()->create_backend_fn_ = MockCreateBackend; + cache.http_cache()->set_type(MEMORY_CACHE); + BlockCacheCreation(true); + + MockHttpRequest request0(kSimpleGET_Transaction); + MockHttpRequest request1(kTypicalGET_Transaction); + MockHttpRequest request2(kETagGET_Transaction); + + std::vector<Context*> context_list; + const int kNumTransactions = 3; + + for (int i = 0; i < kNumTransactions; i++) { + context_list.push_back(new Context()); + Context* c = context_list[i]; + + c->result = cache.http_cache()->CreateTransaction(&c->trans); + EXPECT_EQ(OK, c->result); + } + + context_list[0]->result = context_list[0]->trans->Start( + &request0, &context_list[0]->callback, BoundNetLog()); + context_list[1]->result = context_list[1]->trans->Start( + &request1, &context_list[1]->callback, BoundNetLog()); + context_list[2]->result = context_list[2]->trans->Start( + &request2, &context_list[2]->callback, BoundNetLog()); + + // Just to make sure that everything is still pending. + MessageLoop::current()->RunAllPending(); + + // The first request should be creating the disk cache. + EXPECT_FALSE(context_list[0]->callback.have_result()); + + // Cancel a request from the pending queue. + delete context_list[1]; + context_list[1] = NULL; + + // Cancel the request that is creating the entry. + delete context_list[0]; + context_list[0] = NULL; + + // Complete the last transaction. + BlockCacheCreation(false); + + context_list[2]->result = + context_list[2]->callback.GetResult(context_list[2]->result); + ReadAndVerifyTransaction(context_list[2]->trans.get(), kETagGET_Transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_EQ(1, cache.disk_cache()->create_count()); + + delete context_list[2]; +} + +} // net namespace. TEST(HttpCache, CreateThenDestroy) { MockHttpCache cache; @@ -912,21 +1081,36 @@ TEST(HttpCache, SimpleGETNoDiskCache) { // Check that the NetLog was filled as expected. // (We attempted to both Open and Create entries, but both failed). - EXPECT_EQ(4u, log.entries().size()); + EXPECT_EQ(6u, log.entries().size()); + EXPECT_TRUE(net::LogContainsBeginEvent( + log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + EXPECT_TRUE(net::LogContainsEndEvent( + log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); + log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); + log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); EXPECT_EQ(0, cache.disk_cache()->create_count()); } +TEST(HttpCache, SimpleGETNoDiskCache2) { + // This will initialize a cache object with NULL backend. + MockHttpCache cache(NULL); + + // Read from the network, and don't use the cache. + RunTransactionTest(cache.http_cache(), kSimpleGET_Transaction); + + EXPECT_EQ(1, cache.network_layer()->transaction_count()); + EXPECT_FALSE(cache.http_cache()->GetBackend()); +} + TEST(HttpCache, SimpleGETWithDiskFailures) { MockHttpCache cache; @@ -996,19 +1180,23 @@ TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) { log.bound()); // Check that the NetLog was filled as expected. - EXPECT_EQ(6u, log.entries().size()); + EXPECT_EQ(8u, log.entries().size()); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); + log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); + log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + EXPECT_TRUE(net::LogContainsBeginEvent( + log.entries(), 6, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + EXPECT_TRUE(net::LogContainsEndEvent( + log.entries(), 7, net::NetLog::TYPE_HTTP_CACHE_WAITING)); // force this transaction to read from the cache MockTransaction transaction(kSimpleGET_Transaction); @@ -1019,19 +1207,23 @@ TEST(HttpCache, SimpleGET_LoadOnlyFromCache_Hit) { RunTransactionTestWithLog(cache.http_cache(), transaction, log.bound()); // Check that the NetLog was filled as expected. - EXPECT_EQ(6u, log.entries().size()); + EXPECT_EQ(8u, log.entries().size()); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); + log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); + log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_OPEN_ENTRY)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_READ_INFO)); + log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_READ_INFO)); + log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + EXPECT_TRUE(net::LogContainsBeginEvent( + log.entries(), 6, net::NetLog::TYPE_HTTP_CACHE_READ_INFO)); + EXPECT_TRUE(net::LogContainsEndEvent( + log.entries(), 7, net::NetLog::TYPE_HTTP_CACHE_READ_INFO)); EXPECT_EQ(1, cache.network_layer()->transaction_count()); EXPECT_EQ(1, cache.disk_cache()->open_count()); @@ -1111,19 +1303,23 @@ TEST(HttpCache, SimpleGET_LoadBypassCache) { RunTransactionTestWithLog(cache.http_cache(), transaction, log.bound()); // Check that the NetLog was filled as expected. - EXPECT_EQ(6u, log.entries().size()); + EXPECT_EQ(8u, log.entries().size()); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY)); + log.entries(), 0, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY)); + log.entries(), 1, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + log.entries(), 2, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + log.entries(), 3, net::NetLog::TYPE_HTTP_CACHE_DOOM_ENTRY)); EXPECT_TRUE(net::LogContainsBeginEvent( - log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + log.entries(), 4, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); EXPECT_TRUE(net::LogContainsEndEvent( - log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + log.entries(), 5, net::NetLog::TYPE_HTTP_CACHE_CREATE_ENTRY)); + EXPECT_TRUE(net::LogContainsBeginEvent( + log.entries(), 6, net::NetLog::TYPE_HTTP_CACHE_WAITING)); + EXPECT_TRUE(net::LogContainsEndEvent( + log.entries(), 7, net::NetLog::TYPE_HTTP_CACHE_WAITING)); EXPECT_EQ(2, cache.network_layer()->transaction_count()); EXPECT_EQ(0, cache.disk_cache()->open_count()); @@ -1668,6 +1864,42 @@ TEST(HttpCache, SimpleGET_AbandonedCacheRead) { MessageLoop::current()->RunAllPending(); } +// Tests that we can delete the HttpCache and deal with queued transactions +// ("waiting for the backend" as opposed to Active or Doomed entries). +TEST(HttpCache, SimpleGET_ManyWriters_DeleteCache) { + scoped_ptr<MockHttpCache> cache(new MockHttpCache(new MockDiskCacheNoCB())); + + MockHttpRequest request(kSimpleGET_Transaction); + + std::vector<Context*> context_list; + const int kNumTransactions = 5; + + for (int i = 0; i < kNumTransactions; i++) { + context_list.push_back(new Context()); + Context* c = context_list[i]; + + c->result = cache->http_cache()->CreateTransaction(&c->trans); + EXPECT_EQ(net::OK, c->result); + + c->result = c->trans->Start(&request, &c->callback, net::BoundNetLog()); + } + + // The first request should be creating the disk cache entry and the others + // should be pending. + + EXPECT_EQ(0, cache->network_layer()->transaction_count()); + EXPECT_EQ(0, cache->disk_cache()->open_count()); + EXPECT_EQ(0, cache->disk_cache()->create_count()); + + cache.reset(); + + // There is not much to do with the transactions at this point... they are + // waiting for a callback that will not fire. + for (int i = 0; i < kNumTransactions; ++i) { + delete context_list[i]; + } +} + TEST(HttpCache, TypicalGET_ConditionalRequest) { MockHttpCache cache; @@ -3885,12 +4117,12 @@ TEST(HttpCache, SyncRead) { TEST_MODE_SYNC_CACHE_WRITE); MockHttpRequest r1(transaction), - r2(transaction), - r3(transaction); + r2(transaction), + r3(transaction); TestTransactionConsumer c1(cache.http_cache()), - c2(cache.http_cache()), - c3(cache.http_cache()); + c2(cache.http_cache()), + c3(cache.http_cache()); c1.Start(&r1, net::BoundNetLog()); |