Index: net/http/http_cache.cc |
diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc |
index 1dc390a474c3abcc21e95539ed56ef75fb9dda16..d49d778597a221a2adf6412b7567465fd0bafb0f 100644 |
--- a/net/http/http_cache.cc |
+++ b/net/http/http_cache.cc |
@@ -96,28 +96,33 @@ int HttpCache::DefaultBackend::CreateBackend( |
//----------------------------------------------------------------------------- |
HttpCache::ActiveEntry::ActiveEntry(disk_cache::Entry* entry) |
- : disk_entry(entry), |
- writer(NULL), |
- will_process_pending_queue(false), |
- doomed(false) { |
-} |
+ : disk_entry(entry) {} |
HttpCache::ActiveEntry::~ActiveEntry() { |
if (disk_entry) { |
disk_entry->Close(); |
- disk_entry = NULL; |
+ disk_entry = nullptr; |
} |
} |
size_t HttpCache::ActiveEntry::EstimateMemoryUsage() const { |
// Skip |disk_entry| which is tracked in simple_backend_impl; Skip |readers| |
- // and |pending_queue| because the Transactions are owned by their respective |
- // URLRequestHttpJobs. |
+ // and |add_to_entry_queue| because the Transactions are owned by their |
+ // respective URLRequestHttpJobs. |
return 0; |
} |
bool HttpCache::ActiveEntry::HasNoTransactions() { |
- return !writer && readers.empty() && pending_queue.empty(); |
+ return !writer && readers.empty() && add_to_entry_queue.empty() && |
+ validated_queue.empty() && !validating_transaction; |
+} |
+ |
+bool HttpCache::ActiveEntry::HasNoActiveTransactions() { |
+ return !writer && readers.empty() && !validating_transaction; |
+} |
+ |
+bool HttpCache::ActiveEntry::IsReader(Transaction* transaction) { |
+ return readers.count(transaction); |
} |
//----------------------------------------------------------------------------- |
@@ -360,15 +365,18 @@ HttpCache::~HttpCache() { |
weak_factory_.InvalidateWeakPtrs(); |
// If we have any active entries remaining, then we need to deactivate them. |
- // 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. |
+ // We may have some pending tasks to process pending queue for reading or |
+ // parallel validation, but since those won't run (due to our destruction), |
+ // we can simply ignore the corresponding flags. |
while (!active_entries_.empty()) { |
ActiveEntry* entry = active_entries_.begin()->second.get(); |
- entry->will_process_pending_queue = false; |
- entry->pending_queue.clear(); |
+ entry->will_process_waiting_transactions = false; |
+ entry->will_process_parallel_validation = false; |
+ entry->add_to_entry_queue.clear(); |
entry->readers.clear(); |
- entry->writer = NULL; |
+ entry->validated_queue.clear(); |
+ entry->validating_transaction = nullptr; |
+ entry->writer = nullptr; |
DeactivateEntry(entry); |
} |
@@ -628,7 +636,10 @@ int HttpCache::DoomEntry(const std::string& key, Transaction* trans) { |
entry_ptr->doomed = true; |
DCHECK(entry_ptr->writer || !entry_ptr->readers.empty() || |
- entry_ptr->will_process_pending_queue); |
+ !entry_ptr->validated_queue.empty() || |
+ entry_ptr->validating_transaction || |
+ entry_ptr->will_process_waiting_transactions || |
+ entry_ptr->will_process_parallel_validation); |
return OK; |
} |
@@ -684,7 +695,14 @@ void HttpCache::FinalizeDoomedEntry(ActiveEntry* entry) { |
HttpCache::ActiveEntry* HttpCache::FindActiveEntry(const std::string& key) { |
auto it = active_entries_.find(key); |
- return it != active_entries_.end() ? it->second.get() : NULL; |
+ return it != active_entries_.end() ? it->second.get() : nullptr; |
+} |
+ |
+bool HttpCache::IsEntryAlive(const std::string& key, ActiveEntry* entry) { |
+ ActiveEntry* active_entry = FindActiveEntry(key); |
+ if (active_entry && active_entry == entry) |
+ return true; |
+ return doomed_entries_.count(entry); |
} |
HttpCache::ActiveEntry* HttpCache::ActivateEntry( |
@@ -696,7 +714,6 @@ HttpCache::ActiveEntry* HttpCache::ActivateEntry( |
} |
void HttpCache::DeactivateEntry(ActiveEntry* entry) { |
- DCHECK(!entry->will_process_pending_queue); |
DCHECK(!entry->doomed); |
DCHECK(entry->disk_entry); |
DCHECK(entry->HasNoTransactions()); |
@@ -826,97 +843,184 @@ void HttpCache::DestroyEntry(ActiveEntry* entry) { |
} |
} |
-int HttpCache::AddTransactionToEntry(ActiveEntry* entry, Transaction* trans) { |
+int HttpCache::AddTransactionToEntry(ActiveEntry* entry, |
+ Transaction* transaction) { |
DCHECK(entry); |
DCHECK(entry->disk_entry); |
- // 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); |
+ // If a pending queue processing task is posted, then this transaction |
+ // should be added after the queue task is processed, to maintain FIFO |
+ // ordering. |
+ if (entry->will_process_waiting_transactions || |
+ entry->will_process_parallel_validation) { |
+ entry->add_to_entry_queue.push_back(transaction); |
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); |
+ int rv = AddTransactionToEntryImpl(entry, transaction); |
+ if (rv == ERR_IO_PENDING) |
+ entry->add_to_entry_queue.push_back(transaction); |
+ return rv; |
+} |
+ |
+// We implement a basic reader/writer lock for the disk cache entry. If there |
+// is a writer, then all read-only transactions must wait. Non read-only |
+// transactions can proceed to their validation phase. Validation is allowed |
+// for one transaction at a time so that we do not end up with wasted network |
+// requests. |
+int HttpCache::AddTransactionToEntryImpl(ActiveEntry* entry, |
+ Transaction* transaction) { |
+ if (transaction->mode() & Transaction::WRITE) |
+ return AddTransactionToEntryForWrite(entry, transaction); |
+ return AddTransactionToEntryForRead(entry, transaction); |
+} |
+ |
+int HttpCache::AddTransactionToEntryForRead(ActiveEntry* entry, |
+ Transaction* transaction) { |
+ if (entry->writer) { |
+ return ERR_IO_PENDING; |
} |
+ entry->readers.insert(transaction); |
+ if (!entry->add_to_entry_queue.empty()) |
+ ProcessWaitingTransactions(entry); |
+ return OK; |
+} |
- // 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); |
+int HttpCache::AddTransactionToEntryForWrite(ActiveEntry* entry, |
+ Transaction* transaction) { |
+ // A transaction that has the write mode can become the next validating |
+ // transaction. It can also become the writer if there is no active writer at |
+ // this time. |
+ if (entry->validating_transaction) { |
+ return ERR_IO_PENDING; |
+ } |
+ entry->validating_transaction = transaction; |
+ if (!entry->writer && entry->readers.empty()) { |
+ entry->writer = transaction; |
+ } |
+ // If there is no writer, we can add more readers. |
+ if (!entry->writer && !entry->add_to_entry_queue.empty()) |
+ ProcessWaitingTransactions(entry); |
return OK; |
} |
-void HttpCache::DoneWithEntry(ActiveEntry* entry, Transaction* trans, |
+int HttpCache::DoneValidationWithEntry(ActiveEntry* entry, |
+ Transaction* transaction, |
+ bool is_match) { |
+ DCHECK(entry->writer == transaction || |
+ entry->validating_transaction == transaction); |
+ entry->validating_transaction = nullptr; |
+ |
+ if (!is_match) { |
+ if (entry->writer == transaction) |
+ entry->writer = nullptr; |
+ if (entry->HasNoActiveTransactions()) |
+ DestroyEntryRestartPendingTransactions(entry); |
+ else |
+ DoomEntryRestartPendingTransactions(entry); |
+ return OK; |
+ } |
+ |
+ int rv = OK; |
+ // If another transaction is writing the response, let this transaction wait |
+ // till the response is complete. |
+ if (entry->writer != transaction) { |
+ entry->validated_queue.push_back(transaction); |
+ rv = ERR_IO_PENDING; |
+ } |
+ // Else the transaction should either be the writer if its responsible for |
+ // writing the response or should have been converted to a reader. |
+ |
+ // In all cases another transaction can start its validation phase. |
+ ProcessParallelValidation(entry); |
+ return rv; |
+} |
+ |
+void HttpCache::DoneValidationWriteEntry(ActiveEntry* entry, |
+ Transaction* transaction) { |
+ entry->validating_transaction = nullptr; |
+ ProcessParallelValidation(entry); |
+} |
+ |
+void HttpCache::DoneWithEntry(ActiveEntry* entry, |
+ Transaction* transaction, |
bool cancel) { |
// 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()) |
+ if (entry->will_process_waiting_transactions && entry->writer == transaction) |
return; |
- 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 = trans->AddTruncatedFlag(); |
- // The previous operation may have deleted the entry. |
- if (!trans->entry()) |
- return; |
- } |
- DoneWritingToEntry(entry, success); |
- } else { |
- DoneReadingFromEntry(entry, trans); |
+ // Transaction is waiting in the validated_queue. |
+ auto i = std::find(entry->validated_queue.begin(), |
+ entry->validated_queue.end(), transaction); |
+ if (i != entry->validated_queue.end()) { |
+ entry->validated_queue.erase(i); |
+ return; |
+ } |
+ |
+ // Assume there was a failure. |
+ bool success = false; |
+ if (transaction == entry->writer && cancel) { |
+ DCHECK(entry->disk_entry); |
+ // This is a successful operation in the sense that we want to keep the |
+ // entry. |
+ success = transaction->AddTruncatedFlag(); |
+ // The previous operation may have deleted the entry. |
+ if (!transaction->entry()) |
+ return; |
} |
+ |
+ // Transaction is writing to the entry, either in the validation phase or |
+ // response-body phase. |
+ if (transaction == entry->writer || |
+ transaction == entry->validating_transaction) { |
+ DoneWritingToEntry(entry, success, transaction); |
+ return; |
+ } |
+ |
+ // Transaction is reading from the entry. |
+ DoneReadingFromEntry(entry, transaction); |
} |
-void HttpCache::DoneWritingToEntry(ActiveEntry* entry, bool success) { |
- DCHECK(entry->readers.empty()); |
+void HttpCache::DoneWritingToEntry(ActiveEntry* entry, |
+ bool success, |
+ Transaction* transaction) { |
+ DCHECK(transaction == entry->writer || |
+ transaction == entry->validating_transaction); |
- entry->writer = NULL; |
+ if (transaction == entry->writer) |
+ entry->writer = nullptr; |
- if (success) { |
- ProcessPendingQueue(entry); |
- } else { |
- DCHECK(!entry->will_process_pending_queue); |
+ // Transaction is done writing to entry in the validation phase. |
+ if (transaction == entry->validating_transaction) { |
+ entry->validating_transaction = nullptr; |
- // We failed to create this entry. |
- TransactionList pending_queue; |
- pending_queue.swap(entry->pending_queue); |
+ if (!entry->writer) |
+ DoneWritingToEntryProcessOtherTransactions(entry, success); |
+ return; |
+ } |
- entry->disk_entry->Doom(); |
- DestroyEntry(entry); |
+ // Transaction is done writing to entry in the response body phase. |
+ // Whether its a failure or success, validating transaction can be the new |
+ // writer. |
+ if (entry->validating_transaction) { |
+ entry->writer = entry->validating_transaction; |
+ return; |
+ } |
- // 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(); |
- } |
+ DoneWritingToEntryProcessOtherTransactions(entry, success); |
+} |
+ |
+void HttpCache::DoneWritingToEntryProcessOtherTransactions(ActiveEntry* entry, |
+ bool success) { |
+ if (success) { |
+ DCHECK(entry->readers.empty()); |
+ ProcessWaitingTransactions(entry); |
+ return; |
} |
+ |
+ DestroyEntryRestartPendingTransactions(entry); |
} |
void HttpCache::DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans) { |
@@ -927,20 +1031,29 @@ void HttpCache::DoneReadingFromEntry(ActiveEntry* entry, Transaction* trans) { |
entry->readers.erase(it); |
- ProcessPendingQueue(entry); |
+ ProcessWaitingTransactions(entry); |
} |
-void HttpCache::ConvertWriterToReader(ActiveEntry* entry) { |
- DCHECK(entry->writer); |
- DCHECK(entry->writer->mode() == Transaction::READ_WRITE); |
- DCHECK(entry->readers.empty()); |
+bool HttpCache::ConvertWriterToReader(ActiveEntry* entry, |
+ Transaction* transaction) { |
+ DCHECK_EQ(entry->validating_transaction, transaction); |
+ DCHECK_EQ(transaction->mode(), Transaction::READ_WRITE); |
- Transaction* trans = entry->writer; |
+ // We need to differentiate between the scenarios when the entry is already |
+ // written to the cache vs. when the entry is not completely written and this |
+ // is a validating transaction. In the latter case, it cannot become the |
+ // reader and will be added to the validated_queue when |
+ // DoneValidationWithEntry will be called for this transaction. |
- entry->writer = NULL; |
- entry->readers.insert(trans); |
+ // Entry is not completely written yet. |
+ if (entry->writer && entry->writer != transaction) |
+ return false; |
- ProcessPendingQueue(entry); |
+ entry->validating_transaction = nullptr; |
+ entry->writer = nullptr; |
+ entry->readers.insert(transaction); |
+ ProcessWaitingTransactions(entry); |
+ return true; |
} |
LoadState HttpCache::GetLoadStateForPendingTransaction( |
@@ -990,14 +1103,15 @@ void HttpCache::RemovePendingTransaction(Transaction* trans) { |
} |
bool HttpCache::RemovePendingTransactionFromEntry(ActiveEntry* entry, |
- Transaction* trans) { |
- TransactionList& pending_queue = entry->pending_queue; |
+ Transaction* transaction) { |
+ TransactionList& add_to_entry_queue = entry->add_to_entry_queue; |
- auto j = find(pending_queue.begin(), pending_queue.end(), trans); |
- if (j == pending_queue.end()) |
+ auto j = |
+ find(add_to_entry_queue.begin(), add_to_entry_queue.end(), transaction); |
+ if (j == add_to_entry_queue.end()) |
return false; |
- pending_queue.erase(j); |
+ add_to_entry_queue.erase(j); |
return true; |
} |
@@ -1019,22 +1133,89 @@ bool HttpCache::RemovePendingTransactionFromPendingOp(PendingOp* pending_op, |
return false; |
} |
-void HttpCache::ProcessPendingQueue(ActiveEntry* entry) { |
+void HttpCache::ProcessWaitingTransactions(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) |
+ // calls to OnProcessWaitingTransactions. This flag also tells us that we |
+ // should not delete the entry before OnProcessWaitingTransactions runs. |
+ if (entry->will_process_waiting_transactions) |
return; |
- entry->will_process_pending_queue = true; |
+ entry->will_process_waiting_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::OnProcessPendingQueue, GetWeakPtr(), entry)); |
+ FROM_HERE, base::Bind(&HttpCache::OnProcessWaitingTransactions, |
+ GetWeakPtr(), entry->disk_entry->GetKey(), entry)); |
} |
-void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) { |
- entry->will_process_pending_queue = false; |
- DCHECK(!entry->writer); |
+void HttpCache::OnProcessWaitingTransactions(const std::string& key, |
+ ActiveEntry* entry) { |
+ // Check if the entry is still alive either as an active entry or doomed |
+ // entry. |
+ if (!IsEntryAlive(key, entry)) |
+ return; |
+ |
+ entry->will_process_waiting_transactions = false; |
+ |
+ // If no one is interested in this entry, then we can deactivate it. |
+ if (entry->HasNoTransactions()) { |
+ DestroyEntry(entry); |
+ return; |
+ } |
+ |
+ if (entry->validated_queue.empty() && entry->add_to_entry_queue.empty()) |
+ return; |
+ |
+ Transaction* next = nullptr; |
+ TransactionList* list = nullptr; |
+ int rv = OK; |
+ |
+ // To maintain FIFO order of transactions, validated_queue should be processed |
+ // first and then add_to_entry_queue. |
+ if (!entry->validated_queue.empty()) { |
+ next = entry->validated_queue.front(); |
+ list = &entry->validated_queue; |
+ rv = AddTransactionToEntryForRead(entry, next); |
+ } else { |
+ // Promote next transaction from the pending queue. |
+ next = entry->add_to_entry_queue.front(); |
+ list = &entry->add_to_entry_queue; |
+ rv = AddTransactionToEntryImpl(entry, next); |
+ } |
+ |
+ if (rv != ERR_IO_PENDING) { |
+ list->erase(list->begin()); |
+ next->io_callback().Run(rv); |
+ } |
+} |
+ |
+void HttpCache::ProcessParallelValidation(ActiveEntry* entry) { |
+ if (entry->will_process_parallel_validation) |
+ return; |
+ |
+ entry->will_process_parallel_validation = 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::OnProcessParallelValidation, |
+ GetWeakPtr(), entry->disk_entry->GetKey(), entry)); |
+} |
+ |
+void HttpCache::OnProcessParallelValidation(const std::string& key, |
+ ActiveEntry* entry) { |
+ // Check if the entry is still alive either as an active entry or doomed |
+ // entry. |
+ if (!IsEntryAlive(key, entry)) |
+ return; |
+ |
+ entry->will_process_parallel_validation = false; |
+ |
+ // A ProcessWaitingTransactions task before this may add a |
+ // validating_transaction. In that case, do nothing. |
+ if (entry->validating_transaction) |
+ return; |
// If no one is interested in this entry, then we can deactivate it. |
if (entry->HasNoTransactions()) { |
@@ -1042,22 +1223,56 @@ void HttpCache::OnProcessPendingQueue(ActiveEntry* entry) { |
return; |
} |
- if (entry->pending_queue.empty()) |
+ if (entry->add_to_entry_queue.empty()) |
return; |
- // 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. |
+ Transaction* next = entry->add_to_entry_queue.front(); |
+ |
+ int rv = AddTransactionToEntryImpl(entry, next); |
- entry->pending_queue.erase(entry->pending_queue.begin()); |
+ // Read only transaction cannot validate the entry. |
+ if (!(next->mode() & Transaction::WRITE)) |
+ return; |
- int rv = AddTransactionToEntry(entry, next); |
if (rv != ERR_IO_PENDING) { |
+ entry->add_to_entry_queue.erase(entry->add_to_entry_queue.begin()); |
next->io_callback().Run(rv); |
} |
} |
+void HttpCache::DoomEntryRestartPendingTransactions(ActiveEntry* entry) { |
+ DoomActiveEntry(entry->disk_entry->GetKey()); |
+ TransactionList list = GetAllPendingTransactions(entry); |
+ RestartPendingTransactions(list); |
+} |
+ |
+void HttpCache::DestroyEntryRestartPendingTransactions(ActiveEntry* entry) { |
+ entry->disk_entry->Doom(); |
+ TransactionList list = GetAllPendingTransactions(entry); |
+ DestroyEntry(entry); |
+ RestartPendingTransactions(list); |
+} |
+ |
+HttpCache::TransactionList HttpCache::GetAllPendingTransactions( |
+ ActiveEntry* entry) { |
+ TransactionList list; |
+ for (auto* transaction : entry->validated_queue) |
+ list.push_back(transaction); |
+ entry->validated_queue.clear(); |
+ |
+ for (auto* transaction : entry->add_to_entry_queue) |
+ list.push_back(transaction); |
+ entry->add_to_entry_queue.clear(); |
+ |
+ return list; |
+} |
+ |
+void HttpCache::RestartPendingTransactions(const TransactionList& list) { |
+ // ERR_CACHE_RACE causes the transaction to restart the whole process. |
+ for (auto* transaction : list) |
+ transaction->io_callback().Run(ERR_CACHE_RACE); |
+} |
+ |
void HttpCache::OnIOComplete(int result, PendingOp* pending_op) { |
WorkItemOperation op = pending_op->writer->operation(); |