summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
Diffstat (limited to 'net')
-rw-r--r--net/base/net_error_list.h12
-rw-r--r--net/disk_cache/backend_impl.cc8
-rw-r--r--net/disk_cache/backend_impl.h1
-rw-r--r--net/disk_cache/disk_cache.h7
-rw-r--r--net/disk_cache/mem_backend_impl.cc8
-rw-r--r--net/disk_cache/mem_backend_impl.h1
-rw-r--r--net/http/http_cache.cc325
-rw-r--r--net/http/http_cache.h98
-rw-r--r--net/http/http_cache_transaction.cc96
-rw-r--r--net/http/http_cache_transaction.h9
-rw-r--r--net/http/http_cache_unittest.cc318
11 files changed, 730 insertions, 153 deletions
diff --git a/net/base/net_error_list.h b/net/base/net_error_list.h
index 3bc2c3e..4003ee0 100644
--- a/net/base/net_error_list.h
+++ b/net/base/net_error_list.h
@@ -302,5 +302,17 @@ NET_ERROR(CACHE_READ_FAILURE, -401)
// The operation is not supported for this entry.
NET_ERROR(CACHE_OPERATION_NOT_SUPPORTED, -403)
+// The disk cache is unable to open this entry.
+NET_ERROR(CACHE_OPEN_FAILURE, -404)
+
+// The disk cache is unable to create this entry.
+NET_ERROR(CACHE_CREATE_FAILURE, -405)
+
+// Multiple transactions are racing to create disk cache entries. This is an
+// internal error returned from the HttpCache to the HttpCacheTransaction that
+// tells the transaction to restart the entry-creation logic because the state
+// of the cache has changed.
+NET_ERROR(CACHE_RACE, -406)
+
// The server's response was insecure (e.g. there was a cert error).
NET_ERROR(INSECURE_RESPONSE, -501)
diff --git a/net/disk_cache/backend_impl.cc b/net/disk_cache/backend_impl.cc
index 9ab3271..3b3ca6a 100644
--- a/net/disk_cache/backend_impl.cc
+++ b/net/disk_cache/backend_impl.cc
@@ -494,6 +494,14 @@ bool BackendImpl::DoomEntry(const std::string& key) {
return true;
}
+int BackendImpl::DoomEntry(const std::string& key,
+ CompletionCallback* callback) {
+ if (DoomEntry(key))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
bool BackendImpl::DoomAllEntries() {
if (!num_refs_) {
PrepareForRestart();
diff --git a/net/disk_cache/backend_impl.h b/net/disk_cache/backend_impl.h
index b9a288f..6267fac 100644
--- a/net/disk_cache/backend_impl.h
+++ b/net/disk_cache/backend_impl.h
@@ -68,6 +68,7 @@ class BackendImpl : public Backend {
virtual int CreateEntry(const std::string& key, Entry** entry,
CompletionCallback* callback);
virtual bool DoomEntry(const std::string& key);
+ virtual int DoomEntry(const std::string& key, CompletionCallback* callback);
virtual bool DoomAllEntries();
virtual int DoomAllEntries(CompletionCallback* callback);
virtual bool DoomEntriesBetween(const base::Time initial_time,
diff --git a/net/disk_cache/disk_cache.h b/net/disk_cache/disk_cache.h
index c47bd47..3d2793b 100644
--- a/net/disk_cache/disk_cache.h
+++ b/net/disk_cache/disk_cache.h
@@ -113,8 +113,15 @@ class Backend {
CompletionCallback* callback) = 0;
// Marks the entry, specified by the given key, for deletion.
+ // Note: This method is deprecated.
virtual bool DoomEntry(const std::string& key) = 0;
+ // Marks the entry, specified by the given key, for deletion. The return value
+ // is a net error code. If this method returns ERR_IO_PENDING, the |callback|
+ // will be invoked after the entry is doomed.
+ virtual int DoomEntry(const std::string& key,
+ CompletionCallback* callback) = 0;
+
// Marks all entries for deletion.
// Note: This method is deprecated.
virtual bool DoomAllEntries() = 0;
diff --git a/net/disk_cache/mem_backend_impl.cc b/net/disk_cache/mem_backend_impl.cc
index 0b0d7bd..875efdc 100644
--- a/net/disk_cache/mem_backend_impl.cc
+++ b/net/disk_cache/mem_backend_impl.cc
@@ -144,6 +144,14 @@ bool MemBackendImpl::DoomEntry(const std::string& key) {
return true;
}
+int MemBackendImpl::DoomEntry(const std::string& key,
+ CompletionCallback* callback) {
+ if (DoomEntry(key))
+ return net::OK;
+
+ return net::ERR_FAILED;
+}
+
void MemBackendImpl::InternalDoomEntry(MemEntryImpl* entry) {
// Only parent entries can be passed into this method.
DCHECK(entry->type() == MemEntryImpl::kParentEntry);
diff --git a/net/disk_cache/mem_backend_impl.h b/net/disk_cache/mem_backend_impl.h
index 5c9899e..48d196c 100644
--- a/net/disk_cache/mem_backend_impl.h
+++ b/net/disk_cache/mem_backend_impl.h
@@ -35,6 +35,7 @@ class MemBackendImpl : public Backend {
virtual int CreateEntry(const std::string& key, Entry** entry,
CompletionCallback* callback);
virtual bool DoomEntry(const std::string& key);
+ virtual int DoomEntry(const std::string& key, CompletionCallback* callback);
virtual bool DoomAllEntries();
virtual int DoomAllEntries(CompletionCallback* callback);
virtual bool DoomEntriesBetween(const base::Time initial_time,
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc
index 8a76ad1..e2d0e2c 100644
--- a/net/http/http_cache.cc
+++ b/net/http/http_cache.cc
@@ -52,6 +52,78 @@ 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() {}
+
+ disk_cache::Entry* disk_entry;
+ WorkItem* writer;
+ WorkItemList pending_queue;
+};
+
+//-----------------------------------------------------------------------------
+
+// The type of operation represented by a work item.
+enum WorkItemOperation {
+ WI_OPEN_ENTRY,
+ WI_CREATE_ENTRY,
+ WI_DOOM_ENTRY
+};
+
+// A work item encapsulates a single request for cache entry with all the
+// information needed to complete that request.
+class HttpCache::WorkItem {
+ public:
+ WorkItem(ActiveEntry** entry, CompletionCallback* callback,
+ WorkItemOperation operation)
+ : entry_(entry), callback_(callback), operation_(operation) {}
+ ~WorkItem() {}
+ WorkItemOperation operation() { return operation_; }
+
+ // Calls back the transaction with the result of the operation.
+ void NotifyTransaction(int result, ActiveEntry* entry) {
+ if (entry_)
+ *entry_ = entry;
+ if (callback_)
+ callback_->Run(result);
+ }
+
+ void ClearCallback() { callback_ = NULL; }
+ void ClearEntry() { entry_ = NULL; }
+ bool Matches(CompletionCallback* cb) const { return cb == callback_; }
+ bool IsValid() const { return callback_ || entry_; }
+
+ private:
+ ActiveEntry** entry_;
+ CompletionCallback* callback_;
+ WorkItemOperation operation_;
+};
+
+//-----------------------------------------------------------------------------
+
+// This class is a specialized type of CompletionCallback that allows us to
+// 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() {}
+
+ virtual void RunWithParams(const Tuple1<int>& params) {
+ cache_->OnIOComplete(params.a, entry_);
+ delete this;
+ }
+
+ private:
+ HttpCache* cache_;
+ NewEntry* entry_;
+ DISALLOW_COPY_AND_ASSIGN(BackendCallback);
+};
+
+//-----------------------------------------------------------------------------
+
HttpCache::HttpCache(HostResolver* host_resolver,
ProxyService* proxy_service,
SSLConfigService* ssl_config_service,
@@ -242,27 +314,52 @@ std::string HttpCache::GenerateCacheKey(const HttpRequestInfo* request) {
return result;
}
-void HttpCache::DoomEntry(const std::string& key) {
+int HttpCache::DoomEntry(const std::string& key, CompletionCallback* callback) {
// Need to abandon the ActiveEntry, but any transaction attached to the entry
// should not be impacted. Dooming an entry only means that it will no
// longer be returned by FindActiveEntry (and it will also be destroyed once
// all consumers are finished with the entry).
ActiveEntriesMap::iterator it = active_entries_.find(key);
if (it == active_entries_.end()) {
- disk_cache_->DoomEntry(key);
- } else {
- ActiveEntry* entry = it->second;
- active_entries_.erase(it);
+ return AsyncDoomEntry(key, callback);
+ }
- // We keep track of doomed entries so that we can ensure that they are
- // cleaned up properly when the cache is destroyed.
- doomed_entries_.insert(entry);
+ ActiveEntry* entry = it->second;
+ active_entries_.erase(it);
- entry->disk_entry->Doom();
- entry->doomed = true;
+ // We keep track of doomed entries so that we can ensure that they are
+ // cleaned up properly when the cache is destroyed.
+ doomed_entries_.insert(entry);
- DCHECK(entry->writer || !entry->readers.empty());
+ entry->disk_entry->Doom();
+ entry->doomed = true;
+
+ DCHECK(entry->writer || !entry->readers.empty());
+ return OK;
+}
+
+int HttpCache::AsyncDoomEntry(const std::string& key,
+ CompletionCallback* callback) {
+ DCHECK(callback);
+ WorkItem* item = new WorkItem(NULL, callback, WI_DOOM_ENTRY);
+ NewEntry* new_entry = GetNewEntry(key);
+ if (new_entry->writer) {
+ new_entry->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
}
+
+ DCHECK(new_entry->pending_queue.empty());
+
+ new_entry->writer = item;
+ BackendCallback* my_callback = new BackendCallback(this, new_entry);
+
+ int rv = disk_cache_->DoomEntry(key, my_callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearCallback();
+ my_callback->Run(rv);
+ }
+
+ return rv;
}
void HttpCache::FinalizeDoomedEntry(ActiveEntry* entry) {
@@ -283,24 +380,92 @@ HttpCache::ActiveEntry* HttpCache::FindActiveEntry(const std::string& key) {
return it != active_entries_.end() ? it->second : NULL;
}
-HttpCache::ActiveEntry* HttpCache::OpenEntry(const std::string& key) {
+HttpCache::NewEntry* HttpCache::GetNewEntry(const std::string& key) {
DCHECK(!FindActiveEntry(key));
- disk_cache::Entry* disk_entry;
- if (!disk_cache_->OpenEntry(key, &disk_entry))
- return NULL;
+ NewEntriesMap::const_iterator it = new_entries_.find(key);
+ if (it != new_entries_.end())
+ return it->second;
+
+ NewEntry* entry = new NewEntry();
+ new_entries_[key] = entry;
+ return entry;
+}
+
+void HttpCache::DeleteNewEntry(NewEntry* entry) {
+ std::string key;
+ if (entry->disk_entry)
+ key = entry->disk_entry->GetKey();
+
+ if (!key.empty()) {
+ NewEntriesMap::iterator it = new_entries_.find(key);
+ DCHECK(it != new_entries_.end());
+ new_entries_.erase(it);
+ } else {
+ for (NewEntriesMap::iterator it = new_entries_.begin();
+ it != new_entries_.end(); ++it) {
+ if (it->second == entry) {
+ new_entries_.erase(it);
+ break;
+ }
+ }
+ }
+
+ delete entry;
+}
+
+int HttpCache::OpenEntry(const std::string& key, ActiveEntry** entry,
+ CompletionCallback* callback) {
+ ActiveEntry* active_entry = FindActiveEntry(key);
+ if (active_entry) {
+ *entry = active_entry;
+ return OK;
+ }
+
+ WorkItem* item = new WorkItem(entry, callback, WI_OPEN_ENTRY);
+ NewEntry* new_entry = GetNewEntry(key);
+ if (new_entry->writer) {
+ new_entry->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(new_entry->pending_queue.empty());
+
+ new_entry->writer = item;
+ BackendCallback* my_callback = new BackendCallback(this, new_entry);
- return ActivateEntry(key, disk_entry);
+ int rv = disk_cache_->OpenEntry(key, &(new_entry->disk_entry), my_callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearCallback();
+ my_callback->Run(rv);
+ }
+
+ return rv;
}
-HttpCache::ActiveEntry* HttpCache::CreateEntry(const std::string& key) {
+int HttpCache::CreateEntry(const std::string& key, ActiveEntry** entry,
+ CompletionCallback* callback) {
DCHECK(!FindActiveEntry(key));
- disk_cache::Entry* disk_entry;
- if (!disk_cache_->CreateEntry(key, &disk_entry))
- return NULL;
+ WorkItem* item = new WorkItem(entry, callback, WI_CREATE_ENTRY);
+ NewEntry* new_entry = GetNewEntry(key);
+ if (new_entry->writer) {
+ new_entry->pending_queue.push_back(item);
+ return ERR_IO_PENDING;
+ }
+
+ DCHECK(new_entry->pending_queue.empty());
- return ActivateEntry(key, disk_entry);
+ new_entry->writer = item;
+ BackendCallback* my_callback = new BackendCallback(this, new_entry);
+
+ int rv = disk_cache_->CreateEntry(key, &(new_entry->disk_entry), my_callback);
+ if (rv != ERR_IO_PENDING) {
+ item->ClearCallback();
+ my_callback->Run(rv);
+ }
+
+ return rv;
}
void HttpCache::DestroyEntry(ActiveEntry* entry) {
@@ -314,6 +479,7 @@ void HttpCache::DestroyEntry(ActiveEntry* entry) {
HttpCache::ActiveEntry* HttpCache::ActivateEntry(
const std::string& key,
disk_cache::Entry* disk_entry) {
+ DCHECK(!FindActiveEntry(key));
ActiveEntry* entry = new ActiveEntry(disk_entry);
active_entries_[key] = entry;
return entry;
@@ -463,7 +629,8 @@ void HttpCache::ConvertWriterToReader(ActiveEntry* entry) {
ProcessPendingQueue(entry);
}
-void HttpCache::RemovePendingTransaction(Transaction* trans) {
+void HttpCache::RemovePendingTransaction(Transaction* trans,
+ CompletionCallback* cb) {
ActiveEntriesMap::const_iterator i = active_entries_.find(trans->key());
bool found = false;
if (i != active_entries_.end())
@@ -472,9 +639,15 @@ void HttpCache::RemovePendingTransaction(Transaction* trans) {
if (found)
return;
- ActiveEntriesSet::iterator it = doomed_entries_.begin();
- for (; it != doomed_entries_.end() && !found; ++it)
- found = RemovePendingTransactionFromEntry(*it, trans);
+ NewEntriesMap::const_iterator j = new_entries_.find(trans->key());
+ if (j != new_entries_.end())
+ found = RemovePendingCallbackFromNewEntry(j->second, cb);
+
+ ActiveEntriesSet::iterator k = doomed_entries_.begin();
+ for (; k != doomed_entries_.end() && !found; ++k)
+ found = RemovePendingTransactionFromEntry(*k, trans);
+
+ DCHECK(found) << "Pending transaction not found";
}
bool HttpCache::RemovePendingTransactionFromEntry(ActiveEntry* entry,
@@ -490,6 +663,25 @@ bool HttpCache::RemovePendingTransactionFromEntry(ActiveEntry* entry,
return true;
}
+bool HttpCache::RemovePendingCallbackFromNewEntry(NewEntry* entry,
+ CompletionCallback* cb) {
+ if (entry->writer->Matches(cb)) {
+ entry->writer->ClearCallback();
+ entry->writer->ClearEntry();
+ return true;
+ }
+ WorkItemList& pending_queue = entry->pending_queue;
+
+ WorkItemList::iterator it = pending_queue.begin();
+ for (; it != pending_queue.end(); ++it) {
+ if ((*it)->Matches(cb)) {
+ pending_queue.erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
void HttpCache::ProcessPendingQueue(ActiveEntry* entry) {
// Multiple readers may finish with an entry at once, so we want to batch up
// calls to OnProcessPendingQueue. This flag also tells us that we should
@@ -524,6 +716,89 @@ void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) {
AddTransactionToEntry(entry, next);
}
+void HttpCache::OnIOComplete(int result, NewEntry* new_entry) {
+ scoped_ptr<WorkItem> item(new_entry->writer);
+ WorkItemOperation op = item->operation();
+ bool fail_requests = false;
+
+ ActiveEntry* entry = NULL;
+ std::string key;
+ if (result == OK) {
+ if (op == WI_DOOM_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);
+ } else {
+ // The writer transaction is gone.
+ if (op == WI_CREATE_ENTRY)
+ new_entry->disk_entry->Doom();
+ new_entry->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
+ // 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);
+
+ item->NotifyTransaction(result, entry);
+
+ while (!pending_items.empty()) {
+ item.reset(pending_items.front());
+ pending_items.pop_front();
+
+ if (item->operation() == WI_DOOM_ENTRY) {
+ // A queued doom request is always a race.
+ fail_requests = true;
+ } else if (result == OK) {
+ entry = FindActiveEntry(key);
+ if (!entry)
+ fail_requests = true;
+ }
+
+ if (fail_requests) {
+ item->NotifyTransaction(ERR_CACHE_RACE, NULL);
+ continue;
+ }
+
+ if (item->operation() == WI_CREATE_ENTRY) {
+ if (result == OK) {
+ // A second Create request, but the first request succeded.
+ item->NotifyTransaction(ERR_CACHE_CREATE_FAILURE, NULL);
+ } else {
+ if (op != WI_CREATE_ENTRY) {
+ // Failed Open followed by a Create.
+ item->NotifyTransaction(ERR_CACHE_RACE, NULL);
+ fail_requests = true;
+ } else {
+ item->NotifyTransaction(result, entry);
+ }
+ }
+ } else {
+ if (op == WI_CREATE_ENTRY && result != OK) {
+ // Failed Create followed by an Open.
+ item->NotifyTransaction(ERR_CACHE_RACE, NULL);
+ fail_requests = true;
+ } else {
+ item->NotifyTransaction(result, entry);
+ }
+ }
+ }
+}
+
void HttpCache::CloseCurrentConnections() {
net::HttpNetworkLayer* network =
static_cast<net::HttpNetworkLayer*>(network_layer_.get());
diff --git a/net/http/http_cache.h b/net/http/http_cache.h
index 6cd8718..90dac74 100644
--- a/net/http/http_cache.h
+++ b/net/http/http_cache.h
@@ -24,6 +24,7 @@
#include "base/task.h"
#include "base/weak_ptr.h"
#include "net/base/cache_type.h"
+#include "net/base/completion_callback.h"
#include "net/http/http_transaction_factory.h"
namespace disk_cache {
@@ -145,10 +146,14 @@ class HttpCache : public HttpTransactionFactory,
// Types --------------------------------------------------------------------
+ class BackendCallback;
class Transaction;
+ class WorkItem;
friend class Transaction;
+ struct NewEntry; // Info for an entry under construction.
typedef std::list<Transaction*> TransactionList;
+ typedef std::list<WorkItem*> WorkItemList;
struct ActiveEntry {
disk_cache::Entry* disk_entry;
@@ -163,40 +168,112 @@ class HttpCache : public HttpTransactionFactory,
};
typedef base::hash_map<std::string, ActiveEntry*> ActiveEntriesMap;
+ typedef base::hash_map<std::string, NewEntry*> NewEntriesMap;
typedef std::set<ActiveEntry*> ActiveEntriesSet;
// Methods ------------------------------------------------------------------
+ // Generates the cache key for this request.
std::string GenerateCacheKey(const HttpRequestInfo*);
- void DoomEntry(const std::string& key);
+
+ // Dooms the entry selected by |key|. |callback| is used for completion
+ // notification if this function returns ERR_IO_PENDING. The entry can be
+ // currently in use or not.
+ int DoomEntry(const std::string& key, CompletionCallback* callback);
+
+ // Dooms the entry selected by |key|. |callback| is used for completion
+ // notification if this function returns ERR_IO_PENDING. The entry should not
+ // be currently in use.
+ int AsyncDoomEntry(const std::string& key, CompletionCallback* callback);
+
+ // Closes a previously doomed entry.
void FinalizeDoomedEntry(ActiveEntry* entry);
+
+ // Returns an entry that is currently in use and not doomed, or NULL.
ActiveEntry* FindActiveEntry(const std::string& key);
- ActiveEntry* ActivateEntry(const std::string& key, disk_cache::Entry*);
+
+ // Creates a new ActiveEntry and starts tracking it. |disk_entry| is the disk
+ // cache entry that corresponds to the desired |key|.
+ // TODO(rvargas): remove the |key| argument.
+ ActiveEntry* ActivateEntry(const std::string& key,
+ disk_cache::Entry* disk_entry);
+
+ // Deletes an ActiveEntry.
void DeactivateEntry(ActiveEntry* entry);
+
+ // Deletes an ActiveEntry using an exhaustive search.
void SlowDeactivateEntry(ActiveEntry* entry);
- ActiveEntry* OpenEntry(const std::string& key);
- ActiveEntry* CreateEntry(const std::string& cache_key);
+
+ // 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);
+
+ // Deletes a NewEntry.
+ void DeleteNewEntry(NewEntry* entry);
+
+ // Opens the disk cache entry associated with |key|, returning an ActiveEntry
+ // in |*entry|. |callback| is used for completion notification if this
+ // function returns ERR_IO_PENDING.
+ int OpenEntry(const std::string& key, ActiveEntry** entry,
+ CompletionCallback* callback);
+
+ // Creates the disk cache entry associated with |key|, returning an
+ // ActiveEntry in |*entry|. |callback| is used for completion notification if
+ // this function returns ERR_IO_PENDING.
+ int CreateEntry(const std::string& key, ActiveEntry** entry,
+ CompletionCallback* callback);
+
+ // Destroys an ActiveEntry (active or doomed).
void DestroyEntry(ActiveEntry* entry);
+
+ // Adds a transaction to an ActiveEntry.
int AddTransactionToEntry(ActiveEntry* entry, Transaction* trans);
+
+ // Called when the transaction has finished working with this entry. |cancel|
+ // is true if the operation was cancelled by the caller instead of running
+ // to completion.
void DoneWithEntry(ActiveEntry* entry, Transaction* trans, bool cancel);
+
+ // Called when the transaction has finished writting to this entry. |success|
+ // is false if the cache entry should be deleted.
void DoneWritingToEntry(ActiveEntry* entry, bool success);
+
+ // Called when the transaction has finished reading from this entry.
void DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans);
+
+ // Convers the active writter transaction to a reader so that other
+ // transactions can start reading from this entry.
void ConvertWriterToReader(ActiveEntry* entry);
- void RemovePendingTransaction(Transaction* trans);
+
+ // Removes the transaction |trans|, waiting for |callback|, from the pending
+ // list of an entry (NewEntry, active or doomed entry).
+ void RemovePendingTransaction(Transaction* trans, CompletionCallback* cb);
+
+ // Removes the transaction |trans|, from the pending list of |entry|.
bool RemovePendingTransactionFromEntry(ActiveEntry* entry,
Transaction* trans);
- void ProcessPendingQueue(ActiveEntry* entry);
+ // Removes the callback |cb|, from the pending list of |entry|.
+ bool RemovePendingCallbackFromNewEntry(NewEntry* entry,
+ CompletionCallback* cb);
+
+ // Resumes processing the pending list of |entry|.
+ void ProcessPendingQueue(ActiveEntry* entry);
// Events (called via PostTask) ---------------------------------------------
void OnProcessPendingQueue(ActiveEntry* entry);
+ // Callbacks ----------------------------------------------------------------
+
+ // Processes BackendCallback notifications.
+ void OnIOComplete(int result, NewEntry* entry);
+
// Variables ----------------------------------------------------------------
- // used when lazily constructing the disk_cache_
+ // Used when lazily constructing the disk_cache_.
FilePath disk_cache_dir_;
Mode mode_;
@@ -205,12 +282,15 @@ class HttpCache : public HttpTransactionFactory,
scoped_ptr<HttpTransactionFactory> network_layer_;
scoped_ptr<disk_cache::Backend> disk_cache_;
- // The set of active entries indexed by cache key
+ // The set of active entries indexed by cache key.
ActiveEntriesMap active_entries_;
- // The set of doomed entries
+ // The set of doomed entries.
ActiveEntriesSet doomed_entries_;
+ // The set of entries "under construction".
+ NewEntriesMap new_entries_;
+
ScopedRunnableMethodFactory<HttpCache> task_factory_;
bool enable_range_support_;
diff --git a/net/http/http_cache_transaction.cc b/net/http/http_cache_transaction.cc
index d8c51ae..733b2e1 100644
--- a/net/http/http_cache_transaction.cc
+++ b/net/http/http_cache_transaction.cc
@@ -113,11 +113,12 @@ HttpCache::Transaction::Transaction(HttpCache* cache, bool enable_range_support)
enable_range_support_(enable_range_support),
truncated_(false),
server_responded_206_(false),
+ cache_pending_(false),
read_offset_(0),
effective_load_flags_(0),
final_upload_progress_(0),
ALLOW_THIS_IN_INITIALIZER_LIST(
- network_callback_(this, &Transaction::OnIOComplete)),
+ io_callback_(this, &Transaction::OnIOComplete)),
ALLOW_THIS_IN_INITIALIZER_LIST(
cache_callback_(new CancelableCompletionCallback<Transaction>(
this, &Transaction::OnIOComplete))),
@@ -142,8 +143,8 @@ HttpCache::Transaction::~Transaction() {
}
cache_->DoneWithEntry(entry_, this, cancel_request);
- } else {
- cache_->RemovePendingTransaction(this);
+ } else if (cache_pending_) {
+ cache_->RemovePendingTransaction(this, &io_callback_);
}
}
@@ -363,6 +364,7 @@ uint64 HttpCache::Transaction::GetUploadProgress() const {
int HttpCache::Transaction::AddToEntry() {
next_state_ = STATE_INIT_ENTRY;
+ cache_pending_ = false;
return DoLoop(OK);
}
@@ -377,47 +379,50 @@ int HttpCache::Transaction::DoInitEntry() {
return OK;
}
- new_entry_ = cache_->FindActiveEntry(cache_key_);
- if (!new_entry_) {
- next_state_ = STATE_OPEN_ENTRY;
- return OK;
- }
-
- if (mode_ == WRITE) {
- next_state_ = STATE_CREATE_ENTRY;
- return OK;
- }
-
- next_state_ = STATE_ADD_TO_ENTRY;
+ next_state_ = STATE_OPEN_ENTRY;
return OK;
}
int HttpCache::Transaction::DoDoomEntry() {
next_state_ = STATE_DOOM_ENTRY_COMPLETE;
- cache_->DoomEntry(cache_key_);
- return OK;
+ cache_pending_ = true;
+ // TODO(rvargas): Add a LoadLog event.
+ return cache_->DoomEntry(cache_key_, &io_callback_);
}
-int HttpCache::Transaction::DoDoomEntryComplete() {
+int HttpCache::Transaction::DoDoomEntryComplete(int result) {
next_state_ = STATE_CREATE_ENTRY;
+ cache_pending_ = false;
+ if (result == ERR_CACHE_RACE)
+ next_state_ = STATE_INIT_ENTRY;
+
return OK;
}
int HttpCache::Transaction::DoOpenEntry() {
DCHECK(!new_entry_);
next_state_ = STATE_OPEN_ENTRY_COMPLETE;
+ cache_pending_ = true;
LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY);
- new_entry_ = cache_->OpenEntry(cache_key_);
- return OK;
+ return cache_->OpenEntry(cache_key_, &new_entry_, &io_callback_);
}
-int HttpCache::Transaction::DoOpenEntryComplete() {
+int HttpCache::Transaction::DoOpenEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_OPEN_ENTRY);
- if (new_entry_) {
+ cache_pending_ = false;
+ if (result == OK) {
next_state_ = STATE_ADD_TO_ENTRY;
return OK;
}
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
if (mode_ == READ_WRITE) {
mode_ = WRITE;
next_state_ = STATE_CREATE_ENTRY;
@@ -440,15 +445,25 @@ int HttpCache::Transaction::DoOpenEntryComplete() {
int HttpCache::Transaction::DoCreateEntry() {
DCHECK(!new_entry_);
next_state_ = STATE_CREATE_ENTRY_COMPLETE;
+ cache_pending_ = true;
LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY);
- new_entry_ = cache_->CreateEntry(cache_key_);
- return OK;
+ return cache_->CreateEntry(cache_key_, &new_entry_, &io_callback_);
}
-int HttpCache::Transaction::DoCreateEntryComplete() {
+int HttpCache::Transaction::DoCreateEntryComplete(int result) {
+ // It is important that we go to STATE_ADD_TO_ENTRY whenever the result is
+ // OK, otherwise the cache will end up with an active entry without any
+ // transaction attached.
LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_CREATE_ENTRY);
+ cache_pending_ = false;
next_state_ = STATE_ADD_TO_ENTRY;
- if (!new_entry_) {
+
+ if (result == ERR_CACHE_RACE) {
+ next_state_ = STATE_INIT_ENTRY;
+ return OK;
+ }
+
+ if (result != OK) {
// We have a race here: Maybe we failed to open the entry and decided to
// create one, but by the time we called create, another transaction already
// created the entry. If we want to eliminate this issue, we need an atomic
@@ -464,6 +479,7 @@ int HttpCache::Transaction::DoCreateEntryComplete() {
int HttpCache::Transaction::DoAddToEntry() {
DCHECK(new_entry_);
+ cache_pending_ = true;
LoadLog::BeginEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_WAITING);
int rv = cache_->AddTransactionToEntry(new_entry_, this);
new_entry_ = NULL;
@@ -472,6 +488,7 @@ int HttpCache::Transaction::DoAddToEntry() {
int HttpCache::Transaction::EntryAvailable(ActiveEntry* entry) {
LoadLog::EndEvent(load_log_, LoadLog::TYPE_HTTP_CACHE_WAITING);
+ cache_pending_ = false;
entry_ = entry;
next_state_ = STATE_ENTRY_AVAILABLE;
@@ -613,24 +630,21 @@ int HttpCache::Transaction::DoLoop(int result) {
rv = DoOpenEntry();
break;
case STATE_OPEN_ENTRY_COMPLETE:
- DCHECK_EQ(OK, rv);
- rv = DoOpenEntryComplete();
+ rv = DoOpenEntryComplete(rv);
break;
case STATE_CREATE_ENTRY:
DCHECK_EQ(OK, rv);
rv = DoCreateEntry();
break;
case STATE_CREATE_ENTRY_COMPLETE:
- DCHECK_EQ(OK, rv);
- rv = DoCreateEntryComplete();
+ rv = DoCreateEntryComplete(rv);
break;
case STATE_DOOM_ENTRY:
DCHECK_EQ(OK, rv);
rv = DoDoomEntry();
break;
case STATE_DOOM_ENTRY_COMPLETE:
- DCHECK_EQ(OK, rv);
- rv = DoDoomEntryComplete();
+ rv = DoDoomEntryComplete(rv);
break;
case STATE_ADD_TO_ENTRY:
DCHECK_EQ(OK, rv);
@@ -1032,7 +1046,7 @@ int HttpCache::Transaction::DoSendRequest() {
return rv;
next_state_ = STATE_SEND_REQUEST_COMPLETE;
- rv = network_trans_->Start(request_, &network_callback_, load_log_);
+ rv = network_trans_->Start(request_, &io_callback_, load_log_);
return rv;
}
@@ -1042,7 +1056,7 @@ int HttpCache::Transaction::RestartNetworkRequest() {
DCHECK_EQ(STATE_NONE, next_state_);
next_state_ = STATE_SEND_REQUEST_COMPLETE;
- int rv = network_trans_->RestartIgnoringLastError(&network_callback_);
+ int rv = network_trans_->RestartIgnoringLastError(&io_callback_);
if (rv != ERR_IO_PENDING)
return DoLoop(rv);
return rv;
@@ -1055,8 +1069,7 @@ int HttpCache::Transaction::RestartNetworkRequestWithCertificate(
DCHECK_EQ(STATE_NONE, next_state_);
next_state_ = STATE_SEND_REQUEST_COMPLETE;
- int rv = network_trans_->RestartWithCertificate(client_cert,
- &network_callback_);
+ int rv = network_trans_->RestartWithCertificate(client_cert, &io_callback_);
if (rv != ERR_IO_PENDING)
return DoLoop(rv);
return rv;
@@ -1070,8 +1083,7 @@ int HttpCache::Transaction::RestartNetworkRequestWithAuth(
DCHECK_EQ(STATE_NONE, next_state_);
next_state_ = STATE_SEND_REQUEST_COMPLETE;
- int rv = network_trans_->RestartWithAuth(username, password,
- &network_callback_);
+ int rv = network_trans_->RestartWithAuth(username, password, &io_callback_);
if (rv != ERR_IO_PENDING)
return DoLoop(rv);
return rv;
@@ -1282,7 +1294,7 @@ int HttpCache::Transaction::ReadFromNetwork(IOBuffer* data, int data_len) {
int HttpCache::Transaction::DoNetworkRead() {
next_state_ = STATE_NETWORK_READ_COMPLETE;
- return network_trans_->Read(read_buf_, io_buf_len_, &network_callback_);
+ return network_trans_->Read(read_buf_, io_buf_len_, &io_callback_);
}
int HttpCache::Transaction::ReadFromEntry(IOBuffer* data, int data_len) {
@@ -1442,8 +1454,9 @@ void HttpCache::Transaction::DoneWritingToEntry(bool success) {
}
void HttpCache::Transaction::DoomPartialEntry(bool delete_object) {
+ int rv = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, rv);
cache_->DoneWithEntry(entry_, this, false);
- cache_->DoomEntry(cache_key_);
entry_ = NULL;
if (delete_object)
partial_.reset(NULL);
@@ -1579,7 +1592,8 @@ int HttpCache::Transaction::DoUpdateCachedResponse() {
response_.request_time = new_response_->request_time;
if (response_.headers->HasHeaderValue("cache-control", "no-store")) {
- cache_->DoomEntry(cache_key_);
+ int ret = cache_->DoomEntry(cache_key_, NULL);
+ DCHECK_EQ(OK, ret);
} else {
// If we are already reading, we already updated the headers for this
// request; doing it again will change Content-Length.
diff --git a/net/http/http_cache_transaction.h b/net/http/http_cache_transaction.h
index 0e7e51c..f417378 100644
--- a/net/http/http_cache_transaction.h
+++ b/net/http/http_cache_transaction.h
@@ -150,11 +150,11 @@ class HttpCache::Transaction : public HttpTransaction {
int DoNetworkReadComplete(int result);
int DoInitEntry();
int DoOpenEntry();
- int DoOpenEntryComplete();
+ int DoOpenEntryComplete(int result);
int DoCreateEntry();
- int DoCreateEntryComplete();
+ int DoCreateEntryComplete(int result);
int DoDoomEntry();
- int DoDoomEntryComplete();
+ int DoDoomEntryComplete(int result);
int DoAddToEntry();
int DoEntryAvailable();
int DoPartialCacheValidation();
@@ -303,13 +303,14 @@ class HttpCache::Transaction : public HttpTransaction {
bool enable_range_support_;
bool truncated_; // We don't have all the response data.
bool server_responded_206_;
+ bool cache_pending_; // We are waiting for the HttpCache.
scoped_refptr<IOBuffer> read_buf_;
int io_buf_len_;
int read_offset_;
int effective_load_flags_;
scoped_ptr<PartialData> partial_; // We are dealing with range requests.
uint64 final_upload_progress_;
- CompletionCallbackImpl<Transaction> network_callback_;
+ CompletionCallbackImpl<Transaction> io_callback_;
scoped_refptr<CancelableCompletionCallback<Transaction> > cache_callback_;
scoped_refptr<CancelableCompletionCallback<Transaction> >
write_headers_callback_;
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index 8a69c65..72231fb 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -27,6 +27,26 @@ using base::Time;
namespace {
+int GetTestModeForEntry(const std::string& key) {
+ // 'key' is prefixed with an identifier if it corresponds to a cached POST.
+ // Skip past that to locate the actual URL.
+ //
+ // TODO(darin): It breaks the abstraction a bit that we assume 'key' is an
+ // URL corresponding to a registered MockTransaction. It would be good to
+ // have another way to access the test_mode.
+ GURL url;
+ if (isdigit(key[0])) {
+ size_t slash = key.find('/');
+ DCHECK(slash != std::string::npos);
+ url = GURL(key.substr(slash + 1));
+ } else {
+ url = GURL(key);
+ }
+ const MockTransaction* t = FindMockTransaction(url);
+ DCHECK(t);
+ return t->test_mode;
+}
+
//-----------------------------------------------------------------------------
// mock disk cache (a very basic memory cache implementation)
@@ -41,25 +61,7 @@ class MockDiskEntry : public disk_cache::Entry,
explicit MockDiskEntry(const std::string& key)
: key_(key), doomed_(false), sparse_(false), fail_requests_(false),
busy_(false), delayed_(false) {
- //
- // 'key' is prefixed with an identifier if it corresponds to a cached POST.
- // Skip past that to locate the actual URL.
- //
- // TODO(darin): It breaks the abstraction a bit that we assume 'key' is an
- // URL corresponding to a registered MockTransaction. It would be good to
- // have another way to access the test_mode.
- //
- GURL url;
- if (isdigit(key[0])) {
- size_t slash = key.find('/');
- DCHECK(slash != std::string::npos);
- url = GURL(key.substr(slash + 1));
- } else {
- url = GURL(key);
- }
- const MockTransaction* t = FindMockTransaction(url);
- DCHECK(t);
- test_mode_ = t->test_mode;
+ test_mode_ = GetTestModeForEntry(key);
}
bool is_doomed() const { return doomed_; }
@@ -353,17 +355,23 @@ class MockDiskCache : public disk_cache::Backend {
}
virtual bool OpenEntry(const std::string& key, disk_cache::Entry** entry) {
+ NOTREACHED();
+ return false;
+ }
+
+ virtual int OpenEntry(const std::string& key, disk_cache::Entry** entry,
+ net::CompletionCallback* callback) {
if (fail_requests_)
- return false;
+ return net::ERR_CACHE_OPEN_FAILURE;
EntryMap::iterator it = entries_.find(key);
if (it == entries_.end())
- return false;
+ return net::ERR_CACHE_OPEN_FAILURE;
if (it->second->is_doomed()) {
it->second->Release();
entries_.erase(it);
- return false;
+ return net::ERR_CACHE_OPEN_FAILURE;
}
open_count_++;
@@ -374,20 +382,29 @@ class MockDiskCache : public disk_cache::Backend {
if (soft_failures_)
it->second->set_fail_requests();
- return true;
- }
+ if (!callback || (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START))
+ return net::OK;
- virtual int OpenEntry(const std::string& key, disk_cache::Entry** entry,
- net::CompletionCallback* callback) {
- return net::ERR_NOT_IMPLEMENTED;
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
}
virtual bool CreateEntry(const std::string& key, disk_cache::Entry** entry) {
+ NOTREACHED();
+ return false;
+ }
+
+ virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
+ net::CompletionCallback* callback) {
if (fail_requests_)
- return false;
+ return net::ERR_CACHE_CREATE_FAILURE;
EntryMap::iterator it = entries_.find(key);
- DCHECK(it == entries_.end());
+ if (it != entries_.end()) {
+ DCHECK(it->second->is_doomed());
+ it->second->Release();
+ entries_.erase(it);
+ }
create_count_++;
@@ -402,21 +419,30 @@ class MockDiskCache : public disk_cache::Backend {
if (soft_failures_)
new_entry->set_fail_requests();
- return true;
- }
+ if (!callback || (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START))
+ return net::OK;
- virtual int CreateEntry(const std::string& key, disk_cache::Entry** entry,
- net::CompletionCallback* callback) {
- return net::ERR_NOT_IMPLEMENTED;
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
}
virtual bool DoomEntry(const std::string& key) {
+ return false;
+ }
+
+ virtual int DoomEntry(const std::string& key,
+ net::CompletionCallback* callback) {
EntryMap::iterator it = entries_.find(key);
if (it != entries_.end()) {
it->second->Release();
entries_.erase(it);
}
- return true;
+
+ if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
+ return net::OK;
+
+ CallbackLater(callback, net::OK);
+ return net::ERR_IO_PENDING;
}
virtual bool DoomAllEntries() {
@@ -429,7 +455,7 @@ class MockDiskCache : public disk_cache::Backend {
virtual bool DoomEntriesBetween(const Time initial_time,
const Time end_time) {
- return true;
+ return false;
}
virtual int DoomEntriesBetween(const base::Time initial_time,
@@ -439,7 +465,7 @@ class MockDiskCache : public disk_cache::Backend {
}
virtual bool DoomEntriesSince(const Time initial_time) {
- return true;
+ return false;
}
virtual int DoomEntriesSince(const base::Time initial_time,
@@ -476,6 +502,26 @@ class MockDiskCache : public disk_cache::Backend {
private:
typedef base::hash_map<std::string, MockDiskEntry*> EntryMap;
+
+ class CallbackRunner : public Task {
+ public:
+ CallbackRunner(net::CompletionCallback* callback, int result)
+ : callback_(callback), result_(result) {}
+ virtual void Run() {
+ callback_->Run(result_);
+ }
+
+ private:
+ net::CompletionCallback* callback_;
+ int result_;
+ DISALLOW_COPY_AND_ASSIGN(CallbackRunner);
+ };
+
+ void CallbackLater(net::CompletionCallback* callback, int result) {
+ MessageLoop::current()->PostTask(FROM_HERE,
+ new CallbackRunner(callback, result));
+ }
+
EntryMap entries_;
int open_count_;
int create_count_;
@@ -913,7 +959,8 @@ TEST(HttpCache, SimpleGETWithDiskFailures2) {
// We have to open the entry again to propagate the failure flag.
disk_cache::Entry* en;
- ASSERT_TRUE(cache.disk_cache()->OpenEntry(kSimpleGET_Transaction.url, &en));
+ ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kSimpleGET_Transaction.url,
+ &en, NULL));
en->Close();
ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
@@ -1198,7 +1245,10 @@ TEST(HttpCache, SimpleGET_ManyReaders) {
c->result = c->trans->Start(&request, &c->callback, NULL);
}
- // the first request should be a writer at this point, and the subsequent
+ // Allow all requests to move from the Create queue to the active entry.
+ MessageLoop::current()->RunAllPending();
+
+ // The first request should be a writer at this point, and the subsequent
// requests should be pending.
EXPECT_EQ(1, cache.network_layer()->transaction_count());
@@ -1212,7 +1262,7 @@ TEST(HttpCache, SimpleGET_ManyReaders) {
ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
}
- // we should not have had to re-open the disk entry
+ // We should not have had to re-open the disk entry
EXPECT_EQ(1, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
@@ -1252,6 +1302,9 @@ TEST(HttpCache, SimpleGET_RacingReaders) {
c->result = c->trans->Start(this_request, &c->callback, NULL);
}
+ // Allow all requests to move from the Create queue to the active entry.
+ MessageLoop::current()->RunAllPending();
+
// The first request should be a writer at this point, and the subsequent
// requests should be pending.
@@ -1373,6 +1426,9 @@ TEST(HttpCache, FastNoStoreGET_DoneWithPending) {
c->result = c->trans->Start(&request, &c->callback, NULL);
}
+ // Allow all requests to move from the Create queue to the active entry.
+ MessageLoop::current()->RunAllPending();
+
// The first request should be a writer at this point, and the subsequent
// requests should be pending.
@@ -1416,7 +1472,10 @@ TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) {
c->result = c->trans->Start(&request, &c->callback, NULL);
}
- // the first request should be a writer at this point, and the subsequent
+ // Allow all requests to move from the Create queue to the active entry.
+ MessageLoop::current()->RunAllPending();
+
+ // The first request should be a writer at this point, and the subsequent
// requests should be pending.
EXPECT_EQ(1, cache.network_layer()->transaction_count());
@@ -1427,20 +1486,20 @@ TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) {
Context* c = context_list[i];
if (c->result == net::ERR_IO_PENDING)
c->result = c->callback.WaitForResult();
- // destroy only the first transaction
+ // Destroy only the first transaction.
if (i == 0) {
delete c;
context_list[i] = NULL;
}
}
- // complete the rest of the transactions
+ // Complete the rest of the transactions.
for (int i = 1; i < kNumTransactions; ++i) {
Context* c = context_list[i];
ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
}
- // we should have had to re-open the disk entry
+ // We should have had to re-open the disk entry.
EXPECT_EQ(2, cache.network_layer()->transaction_count());
EXPECT_EQ(0, cache.disk_cache()->open_count());
@@ -1452,6 +1511,107 @@ TEST(HttpCache, SimpleGET_ManyWriters_CancelFirst) {
}
}
+// Tests that we can cancel requests that are queued waiting to open the disk
+// cache entry.
+TEST(HttpCache, SimpleGET_ManyWriters_CancelCreate) {
+ MockHttpCache cache;
+
+ 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, NULL);
+ }
+
+ // 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(1, cache.disk_cache()->create_count());
+
+ // Cancel a request from the pending queue.
+ delete context_list[3];
+ context_list[3] = NULL;
+
+ // Cancel the request that is creating the entry. This will force the pending
+ // operations to restart.
+ delete context_list[0];
+ context_list[0] = NULL;
+
+ // Complete the rest of the transactions.
+ for (int i = 1; i < kNumTransactions; i++) {
+ Context* c = context_list[i];
+ if (c) {
+ c->result = c->callback.GetResult(c->result);
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+ }
+
+ // We should have had to re-create the disk entry.
+
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(2, cache.disk_cache()->create_count());
+
+ for (int i = 1; i < kNumTransactions; ++i) {
+ delete context_list[i];
+ }
+}
+
+// Tests that we delete/create entries even if multiple requests are queued.
+TEST(HttpCache, SimpleGET_ManyWriters_BypassCache) {
+ MockHttpCache cache;
+
+ MockHttpRequest request(kSimpleGET_Transaction);
+ request.load_flags = net::LOAD_BYPASS_CACHE;
+
+ 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, NULL);
+ }
+
+ // The first request should be deleting 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());
+
+ // Complete the transactions.
+ for (int i = 0; i < kNumTransactions; i++) {
+ Context* c = context_list[i];
+ c->result = c->callback.GetResult(c->result);
+ ReadAndVerifyTransaction(c->trans.get(), kSimpleGET_Transaction);
+ }
+
+ // We should have had to re-create the disk entry multiple times.
+
+ EXPECT_EQ(5, cache.network_layer()->transaction_count());
+ EXPECT_EQ(0, cache.disk_cache()->open_count());
+ EXPECT_EQ(5, cache.disk_cache()->create_count());
+
+ for (int i = 0; i < kNumTransactions; ++i) {
+ delete context_list[i];
+ }
+}
+
TEST(HttpCache, SimpleGET_AbandonedCacheRead) {
MockHttpCache cache;
@@ -2626,8 +2786,8 @@ TEST(HttpCache, GET_Previous206_NotSparse) {
// Create a disk cache entry that stores 206 headers while not being sparse.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->CreateEntry(kSimpleGET_Transaction.url,
- &entry));
+ ASSERT_EQ(net::OK, cache.disk_cache()->CreateEntry(kSimpleGET_Transaction.url,
+ &entry, NULL));
std::string raw_headers(kRangeGET_TransactionOK.status);
raw_headers.append("\n");
@@ -2669,8 +2829,9 @@ TEST(HttpCache, RangeGET_Previous206_NotSparse_2) {
// Create a disk cache entry that stores 206 headers while not being sparse.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url,
- &entry));
+ ASSERT_EQ(net::OK,
+ cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url,
+ &entry, NULL));
std::string raw_headers(kRangeGET_TransactionOK.status);
raw_headers.append("\n");
@@ -2858,8 +3019,8 @@ TEST(HttpCache, RangeGET_Cancel) {
// Verify that the entry has not been deleted.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
- &entry));
+ ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
+ &entry, NULL));
entry->Close();
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
@@ -2992,8 +3153,9 @@ TEST(HttpCache, RangeGET_InvalidResponse1) {
EXPECT_EQ(1, cache.disk_cache()->create_count());
// Verify that we don't have a cached entry.
- disk_cache::Entry* en;
- ASSERT_FALSE(cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url, &en));
+ disk_cache::Entry* entry;
+ EXPECT_NE(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
+ &entry, NULL));
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
@@ -3021,8 +3183,9 @@ TEST(HttpCache, RangeGET_InvalidResponse2) {
EXPECT_EQ(1, cache.disk_cache()->create_count());
// Verify that we don't have a cached entry.
- disk_cache::Entry* en;
- ASSERT_FALSE(cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url, &en));
+ disk_cache::Entry* entry;
+ EXPECT_NE(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
+ &entry, NULL));
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
@@ -3063,7 +3226,8 @@ TEST(HttpCache, RangeGET_InvalidResponse3) {
// Verify that we cached the first response but not the second one.
disk_cache::Entry* en;
- ASSERT_TRUE(cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url, &en));
+ ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
+ &en, NULL));
int64 cached_start = 0;
EXPECT_EQ(10, en->GetAvailableRange(40, 20, &cached_start));
EXPECT_EQ(50, cached_start);
@@ -3211,7 +3375,8 @@ TEST(HttpCache, RangeGET_OK_LoadOnlyFromCache) {
TEST(HttpCache, WriteResponseInfo_Truncated) {
MockHttpCache cache;
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->CreateEntry("http://www.google.com", &entry));
+ ASSERT_EQ(net::OK, cache.disk_cache()->CreateEntry("http://www.google.com",
+ &entry, NULL));
std::string headers("HTTP/1.1 200 OK");
headers = net::HttpUtil::AssembleRawHeaders(headers.data(), headers.size());
@@ -3383,8 +3548,8 @@ TEST(HttpCache, Set_Truncated_Flag) {
// Verify that the entry is marked as incomplete.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->OpenEntry(kSimpleGET_Transaction.url,
- &entry));
+ ASSERT_EQ(net::OK, cache.disk_cache()->OpenEntry(kSimpleGET_Transaction.url,
+ &entry, NULL));
net::HttpResponseInfo response;
bool truncated = false;
EXPECT_TRUE(net::HttpCache::ReadResponseInfo(entry, &response, &truncated));
@@ -3402,8 +3567,9 @@ TEST(HttpCache, GET_IncompleteResource) {
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url,
- &entry));
+ ASSERT_EQ(net::OK,
+ cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
+ NULL));
std::string raw_headers("HTTP/1.1 200 OK\n"
"Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
@@ -3462,8 +3628,10 @@ TEST(HttpCache, GET_IncompleteResource2) {
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url,
- &entry));
+ ASSERT_EQ(net::OK,
+ cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
+ NULL));
+
// Content-length will be intentionally bad.
std::string raw_headers("HTTP/1.1 200 OK\n"
@@ -3508,8 +3676,8 @@ TEST(HttpCache, GET_IncompleteResource2) {
RemoveMockTransaction(&kRangeGET_TransactionOK);
// Verify that the disk entry was deleted.
- EXPECT_FALSE(cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
- &entry));
+ ASSERT_NE(net::OK, cache.disk_cache()->OpenEntry(kRangeGET_TransactionOK.url,
+ &entry, NULL));
}
// Tests that when we cancel a request that was interrupted, we mark it again
@@ -3521,8 +3689,9 @@ TEST(HttpCache, GET_CancelIncompleteResource) {
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url,
- &entry));
+ ASSERT_EQ(net::OK,
+ cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
+ NULL));
std::string raw_headers("HTTP/1.1 200 OK\n"
"Last-Modified: Sat, 18 Apr 2009 01:10:43 GMT\n"
@@ -3586,8 +3755,9 @@ TEST(HttpCache, RangeGET_IncompleteResource) {
// Create a disk cache entry that stores an incomplete resource.
disk_cache::Entry* entry;
- ASSERT_TRUE(cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url,
- &entry));
+ ASSERT_EQ(net::OK,
+ cache.disk_cache()->CreateEntry(kRangeGET_TransactionOK.url, &entry,
+ NULL));
// Content-length will be intentionally bogus.
std::string raw_headers("HTTP/1.1 200 OK\n"
@@ -3766,8 +3936,8 @@ TEST(HttpCache, CacheControlNoStore) {
EXPECT_EQ(2, cache.disk_cache()->create_count());
disk_cache::Entry* entry;
- bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
- EXPECT_FALSE(exists);
+ EXPECT_NE(net::OK,
+ cache.disk_cache()->OpenEntry(transaction.url, &entry, NULL));
}
TEST(HttpCache, CacheControlNoStore2) {
@@ -3795,8 +3965,8 @@ TEST(HttpCache, CacheControlNoStore2) {
EXPECT_EQ(1, cache.disk_cache()->create_count());
disk_cache::Entry* entry;
- bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
- EXPECT_FALSE(exists);
+ EXPECT_NE(net::OK,
+ cache.disk_cache()->OpenEntry(transaction.url, &entry, NULL));
}
TEST(HttpCache, CacheControlNoStore3) {
@@ -3825,8 +3995,8 @@ TEST(HttpCache, CacheControlNoStore3) {
EXPECT_EQ(1, cache.disk_cache()->create_count());
disk_cache::Entry* entry;
- bool exists = cache.disk_cache()->OpenEntry(transaction.url, &entry);
- EXPECT_FALSE(exists);
+ EXPECT_NE(net::OK,
+ cache.disk_cache()->OpenEntry(transaction.url, &entry, NULL));
}
// Ensure that we don't cache requests served over bad HTTPS.