Index: net/http/http_cache.cc |
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc |
index 42f8c171908c920a5529d2bd4eaf3cc0f40965a3..968cffa9f587378ead01e2a406dcfeb2c025f5ed 100644 |
--- a/net/http/http_cache.cc |
+++ b/net/http/http_cache.cc |
@@ -96,29 +96,28 @@ |
//----------------------------------------------------------------------------- |
HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry* entry) |
- : disk_entry(entry) {} |
+ : disk_entry(entry), |
+ writer(NULL), |
+ will_process_pending_queue(false), |
+ doomed(false) { |
+} |
HttpCache::ActiveEntry::~ActiveEntry() { |
if (disk_entry) { |
disk_entry->Close(); |
- disk_entry = nullptr; |
+ disk_entry = NULL; |
} |
} |
size_t HttpCache::ActiveEntry::EstimateMemoryUsage() const { |
// Skip |disk_entry| which is tracked in simple_backend_impl; Skip |readers| |
- // and |add_to_entry_queue| because the Transactions are owned by their |
- // respective URLRequestHttpJobs. |
+ // and |pending_queue| because the Transactions are owned by their respective |
+ // URLRequestHttpJobs. |
return 0; |
} |
bool HttpCache::ActiveEntry::HasNoTransactions() { |
- return !writer && readers.empty() && add_to_entry_queue.empty() && |
- done_headers_queue.empty() && !headers_transaction; |
-} |
- |
-bool HttpCache::ActiveEntry::HasNoActiveTransactions() { |
- return !writer && readers.empty() && !headers_transaction; |
+ return !writer && readers.empty() && pending_queue.empty(); |
} |
//----------------------------------------------------------------------------- |
@@ -343,17 +342,15 @@ |
weak_factory_.InvalidateWeakPtrs(); |
// If we have any active entries remaining, then we need to deactivate them. |
- // We may have some pending tasks to process queued transactions ,but since |
- // those won't run (due to our destruction), we can simply ignore the |
- // corresponding flags. |
+ // We may have some pending calls to OnProcessPendingQueue, but since those |
+ // won't run (due to our destruction), we can simply ignore the corresponding |
+ // will_process_pending_queue flag. |
while (!active_entries_.empty()) { |
ActiveEntry* entry = active_entries_.begin()->second.get(); |
- entry->will_process_queued_transactions = false; |
- entry->add_to_entry_queue.clear(); |
+ entry->will_process_pending_queue = false; |
+ entry->pending_queue.clear(); |
entry->readers.clear(); |
- entry->done_headers_queue.clear(); |
- entry->headers_transaction = nullptr; |
- entry->writer = nullptr; |
+ entry->writer = NULL; |
DeactivateEntry(entry); |
} |
@@ -614,8 +611,7 @@ |
entry_ptr->doomed = true; |
DCHECK(entry_ptr->writer || !entry_ptr->readers.empty() || |
- entry_ptr->headers_transaction || |
- entry_ptr->will_process_queued_transactions); |
+ entry_ptr->will_process_pending_queue); |
return OK; |
} |
@@ -671,7 +667,7 @@ |
HttpCache::ActiveEntry* HttpCache::FindActiveEntry(const std::string& key) { |
auto it = active_entries_.find(key); |
- return it != active_entries_.end() ? it->second.get() : nullptr; |
+ return it != active_entries_.end() ? it->second.get() : NULL; |
} |
HttpCache::ActiveEntry* HttpCache::ActivateEntry( |
@@ -683,7 +679,7 @@ |
} |
void HttpCache::DeactivateEntry(ActiveEntry* entry) { |
- DCHECK(!entry->will_process_queued_transactions); |
+ DCHECK(!entry->will_process_pending_queue); |
DCHECK(!entry->doomed); |
DCHECK(entry->disk_entry); |
DCHECK(entry->HasNoTransactions()); |
@@ -813,261 +809,121 @@ |
} |
} |
-int HttpCache::AddTransactionToEntry(ActiveEntry* entry, |
- Transaction* transaction) { |
+int HttpCache::AddTransactionToEntry(ActiveEntry* entry, Transaction* trans) { |
DCHECK(entry); |
DCHECK(entry->disk_entry); |
- // Always add a new transaction to the queue to maintain FIFO order. |
- entry->add_to_entry_queue.push_back(transaction); |
- ProcessQueuedTransactions(entry); |
- return ERR_IO_PENDING; |
-} |
- |
-int HttpCache::DoneWithResponseHeaders(ActiveEntry* entry, |
- Transaction* transaction) { |
- // If |transaction| is the current writer, do nothing. This can happen for |
- // range requests since they can go back to headers phase after starting to |
- // write. |
- if (entry->writer == transaction) |
- return OK; |
- |
- DCHECK_EQ(entry->headers_transaction, transaction); |
- |
- entry->headers_transaction = nullptr; |
- |
- // If transaction is responsible for writing the response body, then do not go |
- // through done_headers_queue for performance benefit. (Also, in case of |
- // writer transaction, the consumer sometimes depend on synchronous behaviour |
- // e.g. while computing raw headers size. (crbug.com/711766)) |
- if (transaction->mode() & Transaction::WRITE) { |
- DCHECK(entry->done_headers_queue.empty()); |
- DCHECK(!entry->writer); |
- entry->writer = transaction; |
- ProcessQueuedTransactions(entry); |
- return OK; |
- } |
- |
- // If this is not the first transaction in done_headers_queue, it should be a |
- // read-mode transaction. |
- DCHECK(entry->done_headers_queue.empty() || |
- !(transaction->mode() & Transaction::WRITE)); |
- |
- entry->done_headers_queue.push_back(transaction); |
- ProcessQueuedTransactions(entry); |
- return ERR_IO_PENDING; |
-} |
- |
-void HttpCache::DoneWithEntry(ActiveEntry* entry, |
- Transaction* transaction, |
+ |
+ // We implement a basic reader/writer lock for the disk cache entry. If |
+ // there is already a writer, then everyone has to wait for the writer to |
+ // finish before they can access the cache entry. There can be multiple |
+ // readers. |
+ // |
+ // NOTE: If the transaction can only write, then the entry should not be in |
+ // use (since any existing entry should have already been doomed). |
+ |
+ if (entry->writer || entry->will_process_pending_queue) { |
+ entry->pending_queue.push_back(trans); |
+ return ERR_IO_PENDING; |
+ } |
+ |
+ if (trans->mode() & Transaction::WRITE) { |
+ // transaction needs exclusive access to the entry |
+ if (entry->readers.empty()) { |
+ entry->writer = trans; |
+ } else { |
+ entry->pending_queue.push_back(trans); |
+ return ERR_IO_PENDING; |
+ } |
+ } else { |
+ // transaction needs read access to the entry |
+ entry->readers.insert(trans); |
+ } |
+ |
+ // We do this before calling EntryAvailable to force any further calls to |
+ // AddTransactionToEntry to add their transaction to the pending queue, which |
+ // ensures FIFO ordering. |
+ if (!entry->writer && !entry->pending_queue.empty()) |
+ ProcessPendingQueue(entry); |
+ |
+ return OK; |
+} |
+ |
+void HttpCache::DoneWithEntry(ActiveEntry* entry, Transaction* trans, |
bool cancel) { |
- // Transaction is waiting in the done_headers_queue. |
- auto it = std::find(entry->done_headers_queue.begin(), |
- entry->done_headers_queue.end(), transaction); |
- if (it != entry->done_headers_queue.end()) { |
- entry->done_headers_queue.erase(it); |
- if (cancel) |
- ProcessEntryFailure(entry); |
+ // If we already posted a task to move on to the next transaction and this was |
+ // the writer, there is nothing to cancel. |
+ if (entry->will_process_pending_queue && entry->readers.empty()) |
return; |
- } |
- |
- // Transaction is removed in the headers phase. |
- if (transaction == entry->headers_transaction) { |
- // If the response is not written (cancel is true), consider it a failure. |
- DoneWritingToEntry(entry, !cancel, transaction); |
- return; |
- } |
- |
- // Transaction is removed in the writing phase. |
- if (transaction == entry->writer) { |
+ |
+ if (entry->writer) { |
+ DCHECK(trans == entry->writer); |
+ |
// Assume there was a failure. |
bool success = false; |
if (cancel) { |
DCHECK(entry->disk_entry); |
// This is a successful operation in the sense that we want to keep the |
// entry. |
- success = transaction->AddTruncatedFlag(); |
+ success = trans->AddTruncatedFlag(); |
// The previous operation may have deleted the entry. |
- if (!transaction->entry()) |
+ if (!trans->entry()) |
return; |
} |
- DoneWritingToEntry(entry, success, transaction); |
- return; |
- } |
- |
- // Transaction is reading from the entry. |
- DoneReadingFromEntry(entry, transaction); |
-} |
- |
-void HttpCache::DoneWritingToEntry(ActiveEntry* entry, |
- bool success, |
- Transaction* transaction) { |
- DCHECK(transaction == entry->writer || |
- transaction == entry->headers_transaction); |
- |
- if (transaction == entry->writer) |
- entry->writer = nullptr; |
- else |
- entry->headers_transaction = nullptr; |
- |
- // If writer fails, restart the headers_transaction by setting its state. |
- // Since the headers_transactions is awaiting an asynchronous operation |
- // completion, when it's IO callback is invoked, it will be restarted. |
- if (!success && entry->headers_transaction) { |
- entry->headers_transaction->SetValidatingCannotProceed(); |
- entry->headers_transaction = nullptr; |
- DCHECK(entry->HasNoActiveTransactions()); |
- } |
- if (!success) |
- ProcessEntryFailure(entry); |
- else |
- ProcessQueuedTransactions(entry); |
-} |
- |
-void HttpCache::DoneReadingFromEntry(ActiveEntry* entry, |
- Transaction* transaction) { |
+ DoneWritingToEntry(entry, success); |
+ } else { |
+ DoneReadingFromEntry(entry, trans); |
+ } |
+} |
+ |
+void HttpCache::DoneWritingToEntry(ActiveEntry* entry, bool success) { |
+ DCHECK(entry->readers.empty()); |
+ |
+ entry->writer = NULL; |
+ |
+ if (success) { |
+ ProcessPendingQueue(entry); |
+ } else { |
+ DCHECK(!entry->will_process_pending_queue); |
+ |
+ // We failed to create this entry. |
+ TransactionList pending_queue; |
+ pending_queue.swap(entry->pending_queue); |
+ |
+ entry->disk_entry->Doom(); |
+ DestroyEntry(entry); |
+ |
+ // We need to do something about these pending entries, which now need to |
+ // be added to a new entry. |
+ while (!pending_queue.empty()) { |
+ // ERR_CACHE_RACE causes the transaction to restart the whole process. |
+ pending_queue.front()->io_callback().Run(ERR_CACHE_RACE); |
+ pending_queue.pop_front(); |
+ } |
+ } |
+} |
+ |
+void HttpCache::DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans) { |
DCHECK(!entry->writer); |
- auto it = entry->readers.find(transaction); |
+ |
+ auto it = entry->readers.find(trans); |
DCHECK(it != entry->readers.end()); |
+ |
entry->readers.erase(it); |
- ProcessQueuedTransactions(entry); |
-} |
- |
-void HttpCache::RemoveAllQueuedTransactions(ActiveEntry* entry, |
- TransactionList* list) { |
- // Process done_headers_queue before add_to_entry_queue to maintain FIFO |
- // order. |
- for (auto* transaction : entry->done_headers_queue) |
- list->push_back(transaction); |
- entry->done_headers_queue.clear(); |
- |
- for (auto* transaction : entry->add_to_entry_queue) |
- list->push_back(transaction); |
- entry->add_to_entry_queue.clear(); |
-} |
- |
-void HttpCache::ProcessEntryFailure(ActiveEntry* entry) { |
- // Failure case is either writer failing to completely write the response to |
- // the cache or validating transaction received a non-304 response. |
- TransactionList list; |
- if (entry->HasNoActiveTransactions() && |
- !entry->will_process_queued_transactions) { |
- entry->disk_entry->Doom(); |
- RemoveAllQueuedTransactions(entry, &list); |
- DestroyEntry(entry); |
- } else { |
- DoomActiveEntry(entry->disk_entry->GetKey()); |
- RemoveAllQueuedTransactions(entry, &list); |
- } |
- // ERR_CACHE_RACE causes the transaction to restart the whole process. |
- for (auto* transaction : list) |
- transaction->io_callback().Run(net::ERR_CACHE_RACE); |
-} |
- |
-void HttpCache::ProcessQueuedTransactions(ActiveEntry* entry) { |
- // Multiple readers may finish with an entry at once, so we want to batch up |
- // calls to OnProcessQueuedTransactions. This flag also tells us that we |
- // should not delete the entry before OnProcessQueuedTransactions runs. |
- if (entry->will_process_queued_transactions) |
- return; |
- |
- entry->will_process_queued_transactions = true; |
- |
- // Post a task instead of invoking the io callback of another transaction here |
- // to avoid re-entrancy. |
- base::ThreadTaskRunnerHandle::Get()->PostTask( |
- FROM_HERE, |
- base::Bind(&HttpCache::OnProcessQueuedTransactions, GetWeakPtr(), entry)); |
-} |
- |
-void HttpCache::ProcessAddToEntryQueue(ActiveEntry* entry) { |
- DCHECK(!entry->add_to_entry_queue.empty()); |
- |
- // Note the entry may be new or may already have a response body written to |
- // it. In both cases, a transaction needs to wait since only one transaction |
- // can be in the headers phase at a time. |
- if (entry->headers_transaction) { |
- return; |
- } |
- Transaction* transaction = entry->add_to_entry_queue.front(); |
- entry->add_to_entry_queue.erase(entry->add_to_entry_queue.begin()); |
- entry->headers_transaction = transaction; |
- |
- transaction->io_callback().Run(OK); |
-} |
- |
-void HttpCache::ProcessDoneHeadersQueue(ActiveEntry* entry) { |
- DCHECK(!entry->writer); |
- DCHECK(!entry->done_headers_queue.empty()); |
- |
- Transaction* transaction = entry->done_headers_queue.front(); |
- |
- // If this transaction is responsible for writing the response body. |
- if (transaction->mode() & Transaction::WRITE) { |
- entry->writer = transaction; |
- } else { |
- // If a transaction is in front of this queue with only read mode set and |
- // there is no writer, it implies response body is already written, convert |
- // to a reader. |
- auto return_val = entry->readers.insert(transaction); |
- DCHECK_EQ(return_val.second, true); |
- } |
- |
- // Post another task to give a chance to more transactions to either join |
- // readers or another transaction to start parallel validation. |
- ProcessQueuedTransactions(entry); |
- |
- entry->done_headers_queue.erase(entry->done_headers_queue.begin()); |
- transaction->io_callback().Run(OK); |
-} |
- |
-bool HttpCache::CanTransactionWriteResponseHeaders(ActiveEntry* entry, |
- Transaction* transaction, |
- bool is_match) const { |
- if (transaction != entry->headers_transaction) |
- return false; |
- |
- if (!(transaction->mode() & Transaction::WRITE)) |
- return false; |
- |
- // If its not a match then check if it is the transaction responsible for |
- // writing the response body. |
- if (!is_match) { |
- return !entry->writer && entry->done_headers_queue.empty() && |
- entry->readers.empty(); |
- } |
- |
- return true; |
-} |
- |
-bool HttpCache::IsTransactionWritingIncomplete( |
- ActiveEntry* entry, |
- Transaction* transaction, |
- const std::string& method) const { |
- if (transaction == entry->writer) |
- return true; |
- |
- if (method == "HEAD" || method == "DELETE") |
- return false; |
- |
- // Check if transaction is about to start writing to the cache. |
- |
- // Transaction's mode may have been set to NONE if StopCaching was invoked. |
- if (!(transaction->mode() & Transaction::WRITE || |
- transaction->mode() == Transaction::NONE)) { |
- return false; |
- } |
- |
- // If a transaction is completing headers or done with headers phase with |
- // write mode then it should be the future writer. Just checking the front of |
- // done_headers_queue since the rest should anyways be READ mode transactions |
- // as they would be a result of validation match. |
- return transaction == entry->headers_transaction || |
- transaction == entry->done_headers_queue.front(); |
-} |
- |
-bool HttpCache::IsWritingInProgress(ActiveEntry* entry) const { |
- return entry->writer != nullptr; |
+ ProcessPendingQueue(entry); |
+} |
+ |
+void HttpCache::ConvertWriterToReader(ActiveEntry* entry) { |
+ DCHECK(entry->writer); |
+ DCHECK(entry->writer->mode() == Transaction::READ_WRITE); |
+ DCHECK(entry->readers.empty()); |
+ |
+ Transaction* trans = entry->writer; |
+ |
+ entry->writer = NULL; |
+ entry->readers.insert(trans); |
+ |
+ ProcessPendingQueue(entry); |
} |
LoadState HttpCache::GetLoadStateForPendingTransaction( |
@@ -1117,15 +973,14 @@ |
} |
bool HttpCache::RemovePendingTransactionFromEntry(ActiveEntry* entry, |
- Transaction* transaction) { |
- TransactionList& add_to_entry_queue = entry->add_to_entry_queue; |
- |
- auto j = |
- find(add_to_entry_queue.begin(), add_to_entry_queue.end(), transaction); |
- if (j == add_to_entry_queue.end()) |
+ Transaction* trans) { |
+ TransactionList& pending_queue = entry->pending_queue; |
+ |
+ auto j = find(pending_queue.begin(), pending_queue.end(), trans); |
+ if (j == pending_queue.end()) |
return false; |
- add_to_entry_queue.erase(j); |
+ pending_queue.erase(j); |
return true; |
} |
@@ -1147,11 +1002,22 @@ |
return false; |
} |
-void HttpCache::OnProcessQueuedTransactions(ActiveEntry* entry) { |
- entry->will_process_queued_transactions = false; |
- |
- // Note that this function should only invoke one transaction's IO callback |
- // since its possible for IO callbacks' consumers to destroy the cache/entry. |
+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 |
+ // not delete the entry before OnProcessPendingQueue runs. |
+ if (entry->will_process_pending_queue) |
+ return; |
+ entry->will_process_pending_queue = true; |
+ |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, |
+ base::Bind(&HttpCache::OnProcessPendingQueue, GetWeakPtr(), entry)); |
+} |
+ |
+void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) { |
+ entry->will_process_pending_queue = false; |
+ DCHECK(!entry->writer); |
// If no one is interested in this entry, then we can deactivate it. |
if (entry->HasNoTransactions()) { |
@@ -1159,22 +1025,20 @@ |
return; |
} |
- if (entry->done_headers_queue.empty() && entry->add_to_entry_queue.empty()) |
+ if (entry->pending_queue.empty()) |
return; |
- // To maintain FIFO order of transactions, done_headers_queue should be |
- // checked for processing before add_to_entry_queue. |
- |
- // If another transaction is writing the response, let validated transactions |
- // wait till the response is complete. If the response is not yet started, the |
- // done_headers_queue transaction should start writing it. |
- if (!entry->writer && !entry->done_headers_queue.empty()) { |
- ProcessDoneHeadersQueue(entry); |
- return; |
- } |
- |
- if (!entry->add_to_entry_queue.empty()) |
- ProcessAddToEntryQueue(entry); |
+ // Promote next transaction from the pending queue. |
+ Transaction* next = entry->pending_queue.front(); |
+ if ((next->mode() & Transaction::WRITE) && !entry->readers.empty()) |
+ return; // Have to wait. |
+ |
+ entry->pending_queue.erase(entry->pending_queue.begin()); |
+ |
+ int rv = AddTransactionToEntry(entry, next); |
+ if (rv != ERR_IO_PENDING) { |
+ next->io_callback().Run(rv); |
+ } |
} |
void HttpCache::OnIOComplete(int result, PendingOp* pending_op) { |