xref: /aosp_15_r20/external/cronet/net/http/http_cache.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2012 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "net/http/http_cache.h"
6 
7 #include <optional>
8 #include <utility>
9 
10 #include "base/compiler_specific.h"
11 #include "base/containers/span.h"
12 #include "base/feature_list.h"
13 #include "base/files/file_util.h"
14 #include "base/format_macros.h"
15 #include "base/functional/bind.h"
16 #include "base/functional/callback.h"
17 #include "base/functional/callback_helpers.h"
18 #include "base/location.h"
19 #include "base/memory/ptr_util.h"
20 #include "base/memory/raw_ptr.h"
21 #include "base/memory/ref_counted.h"
22 #include "base/metrics/field_trial.h"
23 #include "base/metrics/histogram_macros.h"
24 #include "base/metrics/histogram_macros_local.h"
25 #include "base/numerics/safe_conversions.h"
26 #include "base/pickle.h"
27 #include "base/ranges/algorithm.h"
28 #include "base/strings/strcat.h"
29 #include "base/strings/string_number_conversions.h"
30 #include "base/strings/string_util.h"
31 #include "base/strings/stringprintf.h"
32 #include "base/task/single_thread_task_runner.h"
33 #include "base/time/default_clock.h"
34 #include "build/build_config.h"
35 #include "http_request_info.h"
36 #include "net/base/cache_type.h"
37 #include "net/base/features.h"
38 #include "net/base/io_buffer.h"
39 #include "net/base/load_flags.h"
40 #include "net/base/net_errors.h"
41 #include "net/base/network_anonymization_key.h"
42 #include "net/base/network_isolation_key.h"
43 #include "net/base/upload_data_stream.h"
44 #include "net/disk_cache/disk_cache.h"
45 #include "net/http/http_cache_transaction.h"
46 #include "net/http/http_cache_writers.h"
47 #include "net/http/http_network_layer.h"
48 #include "net/http/http_network_session.h"
49 #include "net/http/http_request_info.h"
50 #include "net/http/http_response_headers.h"
51 #include "net/http/http_response_info.h"
52 #include "net/http/http_util.h"
53 #include "net/log/net_log_with_source.h"
54 #include "net/quic/quic_server_info.h"
55 
56 #if BUILDFLAG(IS_POSIX)
57 #include <unistd.h>
58 #endif
59 
60 namespace net {
61 
62 namespace {
63 // True if any HTTP cache has been initialized.
64 bool g_init_cache = false;
65 
66 // True if split cache is enabled by default. Must be set before any HTTP cache
67 // has been initialized.
68 bool g_enable_split_cache = false;
69 }  // namespace
70 
71 const char HttpCache::kDoubleKeyPrefix[] = "_dk_";
72 const char HttpCache::kDoubleKeySeparator[] = " ";
73 const char HttpCache::kSubframeDocumentResourcePrefix[] = "s_";
74 
DefaultBackend(CacheType type,BackendType backend_type,scoped_refptr<disk_cache::BackendFileOperationsFactory> file_operations_factory,const base::FilePath & path,int max_bytes,bool hard_reset)75 HttpCache::DefaultBackend::DefaultBackend(
76     CacheType type,
77     BackendType backend_type,
78     scoped_refptr<disk_cache::BackendFileOperationsFactory>
79         file_operations_factory,
80     const base::FilePath& path,
81     int max_bytes,
82     bool hard_reset)
83     : type_(type),
84       backend_type_(backend_type),
85       file_operations_factory_(std::move(file_operations_factory)),
86       path_(path),
87       max_bytes_(max_bytes),
88       hard_reset_(hard_reset) {}
89 
90 HttpCache::DefaultBackend::~DefaultBackend() = default;
91 
92 // static
InMemory(int max_bytes)93 std::unique_ptr<HttpCache::BackendFactory> HttpCache::DefaultBackend::InMemory(
94     int max_bytes) {
95   return std::make_unique<DefaultBackend>(MEMORY_CACHE, CACHE_BACKEND_DEFAULT,
96                                           /*file_operations_factory=*/nullptr,
97                                           base::FilePath(), max_bytes, false);
98 }
99 
CreateBackend(NetLog * net_log,base::OnceCallback<void (disk_cache::BackendResult)> callback)100 disk_cache::BackendResult HttpCache::DefaultBackend::CreateBackend(
101     NetLog* net_log,
102     base::OnceCallback<void(disk_cache::BackendResult)> callback) {
103   DCHECK_GE(max_bytes_, 0);
104   disk_cache::ResetHandling reset_handling =
105       hard_reset_ ? disk_cache::ResetHandling::kReset
106                   : disk_cache::ResetHandling::kResetOnError;
107   LOCAL_HISTOGRAM_BOOLEAN("HttpCache.HardReset", hard_reset_);
108 #if BUILDFLAG(IS_ANDROID)
109   if (app_status_listener_getter_) {
110     return disk_cache::CreateCacheBackend(
111         type_, backend_type_, file_operations_factory_, path_, max_bytes_,
112         reset_handling, net_log, std::move(callback),
113         app_status_listener_getter_);
114   }
115 #endif
116   return disk_cache::CreateCacheBackend(
117       type_, backend_type_, file_operations_factory_, path_, max_bytes_,
118       reset_handling, net_log, std::move(callback));
119 }
120 
121 #if BUILDFLAG(IS_ANDROID)
SetAppStatusListenerGetter(disk_cache::ApplicationStatusListenerGetter app_status_listener_getter)122 void HttpCache::DefaultBackend::SetAppStatusListenerGetter(
123     disk_cache::ApplicationStatusListenerGetter app_status_listener_getter) {
124   app_status_listener_getter_ = std::move(app_status_listener_getter);
125 }
126 #endif
127 
128 //-----------------------------------------------------------------------------
129 
ActiveEntry(base::WeakPtr<HttpCache> cache,disk_cache::Entry * entry,bool opened_in)130 HttpCache::ActiveEntry::ActiveEntry(base::WeakPtr<HttpCache> cache,
131                                     disk_cache::Entry* entry,
132                                     bool opened_in)
133     : cache_(std::move(cache)), disk_entry_(entry), opened_(opened_in) {
134   CHECK(disk_entry_);
135   cache_->active_entries_.emplace(disk_entry_->GetKey(),
136                                   base::raw_ref<ActiveEntry>::from_ptr(this));
137 }
138 
~ActiveEntry()139 HttpCache::ActiveEntry::~ActiveEntry() {
140   if (cache_) {
141     if (doomed_) {
142       FinalizeDoomed();
143     } else {
144       Deactivate();
145     }
146   }
147 }
148 
FinalizeDoomed()149 void HttpCache::ActiveEntry::FinalizeDoomed() {
150   CHECK(doomed_);
151 
152   auto it =
153       cache_->doomed_entries_.find(base::raw_ref<ActiveEntry>::from_ptr(this));
154   CHECK(it != cache_->doomed_entries_.end());
155 
156   cache_->doomed_entries_.erase(it);
157 }
158 
Deactivate()159 void HttpCache::ActiveEntry::Deactivate() {
160   CHECK(!doomed_);
161 
162   std::string key = disk_entry_->GetKey();
163   if (key.empty()) {
164     SlowDeactivate();
165     return;
166   }
167 
168   auto it = cache_->active_entries_.find(key);
169   CHECK(it != cache_->active_entries_.end());
170   CHECK(&it->second.get() == this);
171 
172   cache_->active_entries_.erase(it);
173 }
174 
175 // TODO(ricea): Add unit test for this method.
SlowDeactivate()176 void HttpCache::ActiveEntry::SlowDeactivate() {
177   CHECK(cache_);
178   // We don't know this entry's key so we have to find it without it.
179   for (auto it = cache_->active_entries_.begin();
180        it != cache_->active_entries_.end(); ++it) {
181     if (&it->second.get() == this) {
182       cache_->active_entries_.erase(it);
183       return;
184     }
185   }
186 }
187 
TransactionInReaders(Transaction * transaction) const188 bool HttpCache::ActiveEntry::TransactionInReaders(
189     Transaction* transaction) const {
190   return readers_.count(transaction) > 0;
191 }
192 
ReleaseWriters()193 void HttpCache::ActiveEntry::ReleaseWriters() {
194   // May destroy `this`.
195   writers_.reset();
196 }
197 
AddTransactionToWriters(Transaction * transaction,ParallelWritingPattern parallel_writing_pattern)198 void HttpCache::ActiveEntry::AddTransactionToWriters(
199     Transaction* transaction,
200     ParallelWritingPattern parallel_writing_pattern) {
201   CHECK(cache_);
202   if (!writers_) {
203     writers_ =
204         std::make_unique<Writers>(cache_.get(), base::WrapRefCounted(this));
205   } else {
206     ParallelWritingPattern writers_pattern;
207     DCHECK(writers_->CanAddWriters(&writers_pattern));
208     DCHECK_EQ(PARALLEL_WRITING_JOIN, writers_pattern);
209   }
210 
211   Writers::TransactionInfo info(transaction->partial(),
212                                 transaction->is_truncated(),
213                                 *(transaction->GetResponseInfo()));
214 
215   writers_->AddTransaction(transaction, parallel_writing_pattern,
216                            transaction->priority(), info);
217 }
218 
Doom()219 void HttpCache::ActiveEntry::Doom() {
220   doomed_ = true;
221   disk_entry_->Doom();
222 }
223 
RestartHeadersPhaseTransactions()224 void HttpCache::ActiveEntry::RestartHeadersPhaseTransactions() {
225   if (headers_transaction_) {
226     RestartHeadersTransaction();
227   }
228 
229   auto it = done_headers_queue_.begin();
230   while (it != done_headers_queue_.end()) {
231     Transaction* done_headers_transaction = *it;
232     it = done_headers_queue_.erase(it);
233     done_headers_transaction->cache_io_callback().Run(net::ERR_CACHE_RACE);
234   }
235 }
236 
RestartHeadersTransaction()237 void HttpCache::ActiveEntry::RestartHeadersTransaction() {
238   Transaction* headers_transaction = headers_transaction_;
239   headers_transaction_ = nullptr;
240   // May destroy `this`.
241   headers_transaction->SetValidatingCannotProceed();
242 }
243 
ProcessAddToEntryQueue()244 void HttpCache::ActiveEntry::ProcessAddToEntryQueue() {
245   DCHECK(!add_to_entry_queue_.empty());
246 
247   // Note `this` may be new or may already have a response body written to it.
248   // In both cases, a transaction needs to wait since only one transaction can
249   // be in the headers phase at a time.
250   if (headers_transaction_) {
251     return;
252   }
253   Transaction* transaction = add_to_entry_queue_.front();
254   add_to_entry_queue_.erase(add_to_entry_queue_.begin());
255   headers_transaction_ = transaction;
256 
257   transaction->cache_io_callback().Run(OK);
258 }
259 
RemovePendingTransaction(Transaction * transaction)260 bool HttpCache::ActiveEntry::RemovePendingTransaction(
261     Transaction* transaction) {
262   auto j =
263       find(add_to_entry_queue_.begin(), add_to_entry_queue_.end(), transaction);
264   if (j == add_to_entry_queue_.end()) {
265     return false;
266   }
267 
268   add_to_entry_queue_.erase(j);
269   return true;
270 }
271 
TakeAllQueuedTransactions()272 HttpCache::TransactionList HttpCache::ActiveEntry::TakeAllQueuedTransactions() {
273   // Process done_headers_queue before add_to_entry_queue to maintain FIFO
274   // order.
275   TransactionList list = std::move(done_headers_queue_);
276   done_headers_queue_.clear();
277   list.splice(list.end(), add_to_entry_queue_);
278   add_to_entry_queue_.clear();
279   return list;
280 }
281 
CanTransactionWriteResponseHeaders(Transaction * transaction,bool is_partial,bool is_match) const282 bool HttpCache::ActiveEntry::CanTransactionWriteResponseHeaders(
283     Transaction* transaction,
284     bool is_partial,
285     bool is_match) const {
286   // If |transaction| is the current writer, do nothing. This can happen for
287   // range requests since they can go back to headers phase after starting to
288   // write.
289   if (writers_ && writers_->HasTransaction(transaction)) {
290     CHECK(is_partial);
291     return true;
292   }
293 
294   if (transaction != headers_transaction_) {
295     return false;
296   }
297 
298   if (!(transaction->mode() & Transaction::WRITE)) {
299     return false;
300   }
301 
302   // If its not a match then check if it is the transaction responsible for
303   // writing the response body.
304   if (!is_match) {
305     return (!writers_ || writers_->IsEmpty()) && done_headers_queue_.empty() &&
306            readers_.empty();
307   }
308 
309   return true;
310 }
311 
312 //-----------------------------------------------------------------------------
313 
314 // This structure keeps track of work items that are attempting to create or
315 // open cache entries or the backend itself.
316 struct HttpCache::PendingOp {
317   PendingOp() = default;
318   ~PendingOp() = default;
319 
320   raw_ptr<disk_cache::Entry, AcrossTasksDanglingUntriaged> entry = nullptr;
321   bool entry_opened = false;  // rather than created.
322 
323   std::unique_ptr<disk_cache::Backend> backend;
324   std::unique_ptr<WorkItem> writer;
325   // True if there is a posted OnPendingOpComplete() task that might delete
326   // |this| without removing it from |pending_ops_|.  Note that since
327   // OnPendingOpComplete() is static, it will not get cancelled when HttpCache
328   // is destroyed.
329   bool callback_will_delete = false;
330   WorkItemList pending_queue;
331 };
332 
333 //-----------------------------------------------------------------------------
334 
335 // A work item encapsulates a single request to the backend with all the
336 // information needed to complete that request.
337 class HttpCache::WorkItem {
338  public:
WorkItem(WorkItemOperation operation,Transaction * transaction,scoped_refptr<ActiveEntry> * entry)339   WorkItem(WorkItemOperation operation,
340            Transaction* transaction,
341            scoped_refptr<ActiveEntry>* entry)
342       : operation_(operation), transaction_(transaction), entry_(entry) {}
WorkItem(WorkItemOperation operation,Transaction * transaction,CompletionOnceCallback callback)343   WorkItem(WorkItemOperation operation,
344            Transaction* transaction,
345            CompletionOnceCallback callback)
346       : operation_(operation),
347         transaction_(transaction),
348         entry_(nullptr),
349         callback_(std::move(callback)) {}
350   ~WorkItem() = default;
351 
352   // Calls back the transaction with the result of the operation.
NotifyTransaction(int result,scoped_refptr<ActiveEntry> entry)353   void NotifyTransaction(int result, scoped_refptr<ActiveEntry> entry) {
354     if (entry_) {
355       *entry_ = std::move(entry);
356     }
357     if (transaction_) {
358       transaction_->cache_io_callback().Run(result);
359     }
360   }
361 
362   // Notifies the caller about the operation completion. Returns true if the
363   // callback was invoked.
DoCallback(int result)364   bool DoCallback(int result) {
365     if (!callback_.is_null()) {
366       std::move(callback_).Run(result);
367       return true;
368     }
369     return false;
370   }
371 
operation()372   WorkItemOperation operation() { return operation_; }
ClearTransaction()373   void ClearTransaction() { transaction_ = nullptr; }
ClearEntry()374   void ClearEntry() { entry_ = nullptr; }
ClearCallback()375   void ClearCallback() { callback_.Reset(); }
Matches(Transaction * transaction) const376   bool Matches(Transaction* transaction) const {
377     return transaction == transaction_;
378   }
IsValid() const379   bool IsValid() const {
380     return transaction_ || entry_ || !callback_.is_null();
381   }
382 
383  private:
384   WorkItemOperation operation_;
385   raw_ptr<Transaction, DanglingUntriaged> transaction_;
386   raw_ptr<scoped_refptr<ActiveEntry>, DanglingUntriaged> entry_;
387   CompletionOnceCallback callback_;  // User callback.
388 };
389 
390 //-----------------------------------------------------------------------------
391 
HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer,std::unique_ptr<BackendFactory> backend_factory)392 HttpCache::HttpCache(std::unique_ptr<HttpTransactionFactory> network_layer,
393                      std::unique_ptr<BackendFactory> backend_factory)
394     : net_log_(nullptr),
395       backend_factory_(std::move(backend_factory)),
396 
397       network_layer_(std::move(network_layer)),
398       clock_(base::DefaultClock::GetInstance()) {
399   g_init_cache = true;
400   HttpNetworkSession* session = network_layer_->GetSession();
401   // Session may be NULL in unittests.
402   // TODO(mmenke): Seems like tests could be changed to provide a session,
403   // rather than having logic only used in unit tests here.
404   if (!session) {
405     return;
406   }
407 
408   net_log_ = session->net_log();
409 }
410 
~HttpCache()411 HttpCache::~HttpCache() {
412   DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
413   // Transactions should see an invalid cache after this point; otherwise they
414   // could see an inconsistent object (half destroyed).
415   weak_factory_.InvalidateWeakPtrs();
416 
417   active_entries_.clear();
418   doomed_entries_.clear();
419 
420   // Before deleting pending_ops_, we have to make sure that the disk cache is
421   // done with said operations, or it will attempt to use deleted data.
422   disk_cache_.reset();
423 
424   for (auto& pending_it : pending_ops_) {
425     // We are not notifying the transactions about the cache going away, even
426     // though they are waiting for a callback that will never fire.
427     PendingOp* pending_op = pending_it.second;
428     pending_op->writer.reset();
429     bool delete_pending_op = true;
430     if (building_backend_ && pending_op->callback_will_delete) {
431       // If we don't have a backend, when its construction finishes it will
432       // deliver the callbacks.
433       delete_pending_op = false;
434     }
435 
436     pending_op->pending_queue.clear();
437     if (delete_pending_op) {
438       delete pending_op;
439     }
440   }
441 }
442 
GetBackend(disk_cache::Backend ** backend,CompletionOnceCallback callback)443 int HttpCache::GetBackend(disk_cache::Backend** backend,
444                           CompletionOnceCallback callback) {
445   DCHECK(!callback.is_null());
446 
447   if (disk_cache_.get()) {
448     *backend = disk_cache_.get();
449     return OK;
450   }
451 
452   int rv =
453       CreateBackend(base::BindOnce(&HttpCache::ReportGetBackendResult,
454                                    GetWeakPtr(), backend, std::move(callback)));
455   if (rv != net::ERR_IO_PENDING) {
456     *backend = disk_cache_.get();
457   }
458   return rv;
459 }
460 
ReportGetBackendResult(disk_cache::Backend ** backend,CompletionOnceCallback callback,int net_error)461 void HttpCache::ReportGetBackendResult(disk_cache::Backend** backend,
462                                        CompletionOnceCallback callback,
463                                        int net_error) {
464   *backend = disk_cache_.get();
465   std::move(callback).Run(net_error);
466 }
467 
GetCurrentBackend() const468 disk_cache::Backend* HttpCache::GetCurrentBackend() const {
469   return disk_cache_.get();
470 }
471 
472 // static
ParseResponseInfo(const char * data,int len,HttpResponseInfo * response_info,bool * response_truncated)473 bool HttpCache::ParseResponseInfo(const char* data,
474                                   int len,
475                                   HttpResponseInfo* response_info,
476                                   bool* response_truncated) {
477   base::Pickle pickle = base::Pickle::WithUnownedBuffer(
478       base::as_bytes(base::span(data, base::checked_cast<size_t>(len))));
479   return response_info->InitFromPickle(pickle, response_truncated);
480 }
481 
CloseAllConnections(int net_error,const char * net_log_reason_utf8)482 void HttpCache::CloseAllConnections(int net_error,
483                                     const char* net_log_reason_utf8) {
484   HttpNetworkSession* session = GetSession();
485   if (session) {
486     session->CloseAllConnections(net_error, net_log_reason_utf8);
487   }
488 }
489 
CloseIdleConnections(const char * net_log_reason_utf8)490 void HttpCache::CloseIdleConnections(const char* net_log_reason_utf8) {
491   HttpNetworkSession* session = GetSession();
492   if (session) {
493     session->CloseIdleConnections(net_log_reason_utf8);
494   }
495 }
496 
OnExternalCacheHit(const GURL & url,const std::string & http_method,const NetworkIsolationKey & network_isolation_key,bool is_subframe_document_resource,bool used_credentials)497 void HttpCache::OnExternalCacheHit(
498     const GURL& url,
499     const std::string& http_method,
500     const NetworkIsolationKey& network_isolation_key,
501     bool is_subframe_document_resource,
502     bool used_credentials) {
503   if (!disk_cache_.get() || mode_ == DISABLE) {
504     return;
505   }
506 
507   if (IsSplitCacheEnabled() && network_isolation_key.IsTransient()) {
508     return;
509   }
510 
511   HttpRequestInfo request_info;
512   request_info.url = url;
513   request_info.method = http_method;
514   request_info.network_isolation_key = network_isolation_key;
515   request_info.network_anonymization_key =
516       net::NetworkAnonymizationKey::CreateFromNetworkIsolationKey(
517           network_isolation_key);
518 
519   request_info.is_subframe_document_resource = is_subframe_document_resource;
520   if (base::FeatureList::IsEnabled(features::kSplitCacheByIncludeCredentials)) {
521     if (!used_credentials) {
522       request_info.load_flags &= LOAD_DO_NOT_SAVE_COOKIES;
523     } else {
524       request_info.load_flags |= ~LOAD_DO_NOT_SAVE_COOKIES;
525     }
526   }
527 
528   std::string key = *GenerateCacheKeyForRequest(&request_info);
529   disk_cache_->OnExternalCacheHit(key);
530 }
531 
CreateTransaction(RequestPriority priority,std::unique_ptr<HttpTransaction> * transaction)532 int HttpCache::CreateTransaction(
533     RequestPriority priority,
534     std::unique_ptr<HttpTransaction>* transaction) {
535   // Do lazy initialization of disk cache if needed.
536   if (!disk_cache_.get()) {
537     // We don't care about the result.
538     CreateBackend(CompletionOnceCallback());
539   }
540 
541   auto new_transaction =
542       std::make_unique<HttpCache::Transaction>(priority, this);
543   if (bypass_lock_for_test_) {
544     new_transaction->BypassLockForTest();
545   }
546   if (bypass_lock_after_headers_for_test_) {
547     new_transaction->BypassLockAfterHeadersForTest();
548   }
549   if (fail_conditionalization_for_test_) {
550     new_transaction->FailConditionalizationForTest();
551   }
552 
553   *transaction = std::move(new_transaction);
554   return OK;
555 }
556 
GetCache()557 HttpCache* HttpCache::GetCache() {
558   return this;
559 }
560 
GetSession()561 HttpNetworkSession* HttpCache::GetSession() {
562   return network_layer_->GetSession();
563 }
564 
565 std::unique_ptr<HttpTransactionFactory>
SetHttpNetworkTransactionFactoryForTesting(std::unique_ptr<HttpTransactionFactory> new_network_layer)566 HttpCache::SetHttpNetworkTransactionFactoryForTesting(
567     std::unique_ptr<HttpTransactionFactory> new_network_layer) {
568   std::unique_ptr<HttpTransactionFactory> old_network_layer(
569       std::move(network_layer_));
570   network_layer_ = std::move(new_network_layer);
571   return old_network_layer;
572 }
573 
574 // static
GetResourceURLFromHttpCacheKey(const std::string & key)575 std::string HttpCache::GetResourceURLFromHttpCacheKey(const std::string& key) {
576   // The key format is:
577   // credential_key/post_key/[isolation_key]url
578 
579   std::string::size_type pos = 0;
580   pos = key.find('/', pos) + 1;  // Consume credential_key/
581   pos = key.find('/', pos) + 1;  // Consume post_key/
582 
583   // It is a good idea to make this function tolerate invalid input. This can
584   // happen because of disk corruption.
585   if (pos == std::string::npos) {
586     return "";
587   }
588 
589   // Consume [isolation_key].
590   // Search the key to see whether it begins with |kDoubleKeyPrefix|. If so,
591   // then the entry was double-keyed.
592   if (pos == key.find(kDoubleKeyPrefix, pos)) {
593     // Find the rightmost occurrence of |kDoubleKeySeparator|, as when both
594     // the top-frame origin and the initiator are added to the key, there will
595     // be two occurrences of |kDoubleKeySeparator|.  When the cache entry is
596     // originally written to disk, GenerateCacheKey method calls
597     // HttpUtil::SpecForRequest method, which has a DCHECK to ensure that
598     // the original resource url is valid, and hence will not contain the
599     // unescaped whitespace of |kDoubleKeySeparator|.
600     pos = key.rfind(kDoubleKeySeparator);
601     DCHECK_NE(pos, std::string::npos);
602     pos += strlen(kDoubleKeySeparator);
603     DCHECK_LE(pos, key.size() - 1);
604   }
605   return key.substr(pos);
606 }
607 
608 // static
609 // Generate a key that can be used inside the cache.
GenerateCacheKey(const GURL & url,int load_flags,const NetworkIsolationKey & network_isolation_key,int64_t upload_data_identifier,bool is_subframe_document_resource)610 std::optional<std::string> HttpCache::GenerateCacheKey(
611     const GURL& url,
612     int load_flags,
613     const NetworkIsolationKey& network_isolation_key,
614     int64_t upload_data_identifier,
615     bool is_subframe_document_resource) {
616   // The first character of the key may vary depending on whether or not sending
617   // credentials is permitted for this request. This only happens if the
618   // SplitCacheByIncludeCredentials feature is enabled.
619   const char credential_key = (base::FeatureList::IsEnabled(
620                                    features::kSplitCacheByIncludeCredentials) &&
621                                (load_flags & LOAD_DO_NOT_SAVE_COOKIES))
622                                   ? '0'
623                                   : '1';
624 
625   std::string isolation_key;
626   if (IsSplitCacheEnabled()) {
627     // Prepend the key with |kDoubleKeyPrefix| = "_dk_" to mark it as
628     // double-keyed (and makes it an invalid url so that it doesn't get
629     // confused with a single-keyed entry). Separate the origin and url
630     // with invalid whitespace character |kDoubleKeySeparator|.
631     if (network_isolation_key.IsTransient()) {
632       return std::nullopt;
633     }
634     std::string subframe_document_resource_prefix =
635         is_subframe_document_resource ? kSubframeDocumentResourcePrefix : "";
636     isolation_key = base::StrCat(
637         {kDoubleKeyPrefix, subframe_document_resource_prefix,
638          *network_isolation_key.ToCacheKeyString(), kDoubleKeySeparator});
639   }
640 
641   // The key format is:
642   // credential_key/upload_data_identifier/[isolation_key]url
643 
644   // Strip out the reference, username, and password sections of the URL and
645   // concatenate with the credential_key, the post_key, and the network
646   // isolation key if we are splitting the cache.
647   return base::StringPrintf("%c/%" PRId64 "/%s%s", credential_key,
648                             upload_data_identifier, isolation_key.c_str(),
649                             HttpUtil::SpecForRequest(url).c_str());
650 }
651 
652 // static
GenerateCacheKeyForRequest(const HttpRequestInfo * request)653 std::optional<std::string> HttpCache::GenerateCacheKeyForRequest(
654     const HttpRequestInfo* request) {
655   CHECK(request);
656   const int64_t upload_data_identifier =
657       request->upload_data_stream ? request->upload_data_stream->identifier()
658                                   : int64_t(0);
659   return GenerateCacheKey(
660       request->url, request->load_flags, request->network_isolation_key,
661       upload_data_identifier, request->is_subframe_document_resource);
662 }
663 
664 // static
SplitCacheFeatureEnableByDefault()665 void HttpCache::SplitCacheFeatureEnableByDefault() {
666   CHECK(!g_enable_split_cache && !g_init_cache);
667   if (!base::FeatureList::GetInstance()->IsFeatureOverridden(
668           "SplitCacheByNetworkIsolationKey")) {
669     g_enable_split_cache = true;
670   }
671 }
672 
673 // static
IsSplitCacheEnabled()674 bool HttpCache::IsSplitCacheEnabled() {
675   return base::FeatureList::IsEnabled(
676              features::kSplitCacheByNetworkIsolationKey) ||
677          g_enable_split_cache;
678 }
679 
680 // static
ClearGlobalsForTesting()681 void HttpCache::ClearGlobalsForTesting() {
682   // Reset these so that unit tests can work.
683   g_init_cache = false;
684   g_enable_split_cache = false;
685 }
686 
687 //-----------------------------------------------------------------------------
688 
CreateAndSetWorkItem(scoped_refptr<ActiveEntry> * entry,Transaction * transaction,WorkItemOperation operation,PendingOp * pending_op)689 net::Error HttpCache::CreateAndSetWorkItem(scoped_refptr<ActiveEntry>* entry,
690                                            Transaction* transaction,
691                                            WorkItemOperation operation,
692                                            PendingOp* pending_op) {
693   auto item = std::make_unique<WorkItem>(operation, transaction, entry);
694 
695   if (pending_op->writer) {
696     pending_op->pending_queue.push_back(std::move(item));
697     return ERR_IO_PENDING;
698   }
699 
700   DCHECK(pending_op->pending_queue.empty());
701 
702   pending_op->writer = std::move(item);
703   return OK;
704 }
705 
CreateBackend(CompletionOnceCallback callback)706 int HttpCache::CreateBackend(CompletionOnceCallback callback) {
707   DCHECK(!disk_cache_);
708 
709   if (!backend_factory_.get()) {
710     return ERR_FAILED;
711   }
712 
713   building_backend_ = true;
714 
715   const bool callback_is_null = callback.is_null();
716   std::unique_ptr<WorkItem> item = std::make_unique<WorkItem>(
717       WI_CREATE_BACKEND, nullptr, std::move(callback));
718 
719   // This is the only operation that we can do that is not related to any given
720   // entry, so we use an empty key for it.
721   PendingOp* pending_op = GetPendingOp(std::string());
722   if (pending_op->writer) {
723     if (!callback_is_null) {
724       pending_op->pending_queue.push_back(std::move(item));
725     }
726     return ERR_IO_PENDING;
727   }
728 
729   DCHECK(pending_op->pending_queue.empty());
730 
731   pending_op->writer = std::move(item);
732 
733   disk_cache::BackendResult result = backend_factory_->CreateBackend(
734       net_log_, base::BindOnce(&HttpCache::OnPendingBackendCreationOpComplete,
735                                GetWeakPtr(), pending_op));
736   if (result.net_error == ERR_IO_PENDING) {
737     pending_op->callback_will_delete = true;
738     return result.net_error;
739   }
740 
741   pending_op->writer->ClearCallback();
742   int rv = result.net_error;
743   OnPendingBackendCreationOpComplete(GetWeakPtr(), pending_op,
744                                      std::move(result));
745   return rv;
746 }
747 
GetBackendForTransaction(Transaction * transaction)748 int HttpCache::GetBackendForTransaction(Transaction* transaction) {
749   if (disk_cache_.get()) {
750     return OK;
751   }
752 
753   if (!building_backend_) {
754     return ERR_FAILED;
755   }
756 
757   std::unique_ptr<WorkItem> item = std::make_unique<WorkItem>(
758       WI_CREATE_BACKEND, transaction, CompletionOnceCallback());
759   PendingOp* pending_op = GetPendingOp(std::string());
760   DCHECK(pending_op->writer);
761   pending_op->pending_queue.push_back(std::move(item));
762   return ERR_IO_PENDING;
763 }
764 
DoomActiveEntry(const std::string & key)765 void HttpCache::DoomActiveEntry(const std::string& key) {
766   auto it = active_entries_.find(key);
767   if (it == active_entries_.end()) {
768     return;
769   }
770 
771   // This is not a performance critical operation, this is handling an error
772   // condition so it is OK to look up the entry again.
773   int rv = DoomEntry(key, nullptr);
774   DCHECK_EQ(OK, rv);
775 }
776 
DoomEntry(const std::string & key,Transaction * transaction)777 int HttpCache::DoomEntry(const std::string& key, Transaction* transaction) {
778   // Need to abandon the ActiveEntry, but any transaction attached to the entry
779   // should not be impacted.  Dooming an entry only means that it will no longer
780   // be returned by GetActiveEntry (and it will also be destroyed once all
781   // consumers are finished with the entry).
782   auto it = active_entries_.find(key);
783   if (it == active_entries_.end()) {
784     DCHECK(transaction);
785     return AsyncDoomEntry(key, transaction);
786   }
787 
788   raw_ref<ActiveEntry> entry_ref = std::move(it->second);
789   active_entries_.erase(it);
790 
791   // We keep track of doomed entries so that we can ensure that they are
792   // cleaned up properly when the cache is destroyed.
793   ActiveEntry& entry = entry_ref.get();
794   DCHECK_EQ(0u, doomed_entries_.count(entry_ref));
795   doomed_entries_.insert(std::move(entry_ref));
796 
797   entry.Doom();
798 
799   return OK;
800 }
801 
AsyncDoomEntry(const std::string & key,Transaction * transaction)802 int HttpCache::AsyncDoomEntry(const std::string& key,
803                               Transaction* transaction) {
804   PendingOp* pending_op = GetPendingOp(key);
805   int rv =
806       CreateAndSetWorkItem(nullptr, transaction, WI_DOOM_ENTRY, pending_op);
807   if (rv != OK) {
808     return rv;
809   }
810 
811   net::RequestPriority priority =
812       transaction ? transaction->priority() : net::LOWEST;
813   rv = disk_cache_->DoomEntry(key, priority,
814                               base::BindOnce(&HttpCache::OnPendingOpComplete,
815                                              GetWeakPtr(), pending_op));
816   if (rv == ERR_IO_PENDING) {
817     pending_op->callback_will_delete = true;
818     return rv;
819   }
820 
821   pending_op->writer->ClearTransaction();
822   OnPendingOpComplete(GetWeakPtr(), pending_op, rv);
823   return rv;
824 }
825 
DoomMainEntryForUrl(const GURL & url,const NetworkIsolationKey & isolation_key,bool is_subframe_document_resource)826 void HttpCache::DoomMainEntryForUrl(const GURL& url,
827                                     const NetworkIsolationKey& isolation_key,
828                                     bool is_subframe_document_resource) {
829   if (!disk_cache_) {
830     return;
831   }
832 
833   if (IsSplitCacheEnabled() && isolation_key.IsTransient()) {
834     return;
835   }
836 
837   HttpRequestInfo temp_info;
838   temp_info.url = url;
839   temp_info.method = "GET";
840   temp_info.network_isolation_key = isolation_key;
841   temp_info.network_anonymization_key =
842       net::NetworkAnonymizationKey::CreateFromNetworkIsolationKey(
843           isolation_key);
844   temp_info.is_subframe_document_resource = is_subframe_document_resource;
845   std::string key = *GenerateCacheKeyForRequest(&temp_info);
846 
847   // Defer to DoomEntry if there is an active entry, otherwise call
848   // AsyncDoomEntry without triggering a callback.
849   if (active_entries_.count(key)) {
850     DoomEntry(key, nullptr);
851   } else {
852     AsyncDoomEntry(key, nullptr);
853   }
854 }
855 
HasActiveEntry(const std::string & key)856 bool HttpCache::HasActiveEntry(const std::string& key) {
857   return active_entries_.find(key) != active_entries_.end();
858 }
859 
GetActiveEntry(const std::string & key)860 scoped_refptr<HttpCache::ActiveEntry> HttpCache::GetActiveEntry(
861     const std::string& key) {
862   auto it = active_entries_.find(key);
863   return it != active_entries_.end() ? base::WrapRefCounted(&it->second.get())
864                                      : nullptr;
865 }
866 
ActivateEntry(disk_cache::Entry * disk_entry,bool opened)867 scoped_refptr<HttpCache::ActiveEntry> HttpCache::ActivateEntry(
868     disk_cache::Entry* disk_entry,
869     bool opened) {
870   DCHECK(!HasActiveEntry(disk_entry->GetKey()));
871   return base::MakeRefCounted<ActiveEntry>(weak_factory_.GetWeakPtr(),
872                                            disk_entry, opened);
873 }
874 
GetPendingOp(const std::string & key)875 HttpCache::PendingOp* HttpCache::GetPendingOp(const std::string& key) {
876   DCHECK(!HasActiveEntry(key));
877 
878   auto it = pending_ops_.find(key);
879   if (it != pending_ops_.end()) {
880     return it->second;
881   }
882 
883   PendingOp* operation = new PendingOp();
884   pending_ops_[key] = operation;
885   return operation;
886 }
887 
DeletePendingOp(PendingOp * pending_op)888 void HttpCache::DeletePendingOp(PendingOp* pending_op) {
889   std::string key;
890   if (pending_op->entry) {
891     key = pending_op->entry->GetKey();
892   }
893 
894   if (!key.empty()) {
895     auto it = pending_ops_.find(key);
896     DCHECK(it != pending_ops_.end());
897     pending_ops_.erase(it);
898   } else {
899     for (auto it = pending_ops_.begin(); it != pending_ops_.end(); ++it) {
900       if (it->second == pending_op) {
901         pending_ops_.erase(it);
902         break;
903       }
904     }
905   }
906   DCHECK(pending_op->pending_queue.empty());
907 
908   delete pending_op;
909 }
910 
OpenOrCreateEntry(const std::string & key,scoped_refptr<ActiveEntry> * entry,Transaction * transaction)911 int HttpCache::OpenOrCreateEntry(const std::string& key,
912                                  scoped_refptr<ActiveEntry>* entry,
913                                  Transaction* transaction) {
914   DCHECK(!HasActiveEntry(key));
915 
916   PendingOp* pending_op = GetPendingOp(key);
917   int rv = CreateAndSetWorkItem(entry, transaction, WI_OPEN_OR_CREATE_ENTRY,
918                                 pending_op);
919   if (rv != OK) {
920     return rv;
921   }
922 
923   disk_cache::EntryResult entry_result = disk_cache_->OpenOrCreateEntry(
924       key, transaction->priority(),
925       base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
926                      pending_op));
927   rv = entry_result.net_error();
928   if (rv == ERR_IO_PENDING) {
929     pending_op->callback_will_delete = true;
930     return ERR_IO_PENDING;
931   }
932 
933   pending_op->writer->ClearTransaction();
934   OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
935                               std::move(entry_result));
936   return rv;
937 }
938 
OpenEntry(const std::string & key,scoped_refptr<ActiveEntry> * entry,Transaction * transaction)939 int HttpCache::OpenEntry(const std::string& key,
940                          scoped_refptr<ActiveEntry>* entry,
941                          Transaction* transaction) {
942   DCHECK(!HasActiveEntry(key));
943 
944   PendingOp* pending_op = GetPendingOp(key);
945   int rv = CreateAndSetWorkItem(entry, transaction, WI_OPEN_ENTRY, pending_op);
946   if (rv != OK) {
947     return rv;
948   }
949 
950   disk_cache::EntryResult entry_result = disk_cache_->OpenEntry(
951       key, transaction->priority(),
952       base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
953                      pending_op));
954   rv = entry_result.net_error();
955   if (rv == ERR_IO_PENDING) {
956     pending_op->callback_will_delete = true;
957     return ERR_IO_PENDING;
958   }
959 
960   pending_op->writer->ClearTransaction();
961   OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
962                               std::move(entry_result));
963   return rv;
964 }
965 
CreateEntry(const std::string & key,scoped_refptr<ActiveEntry> * entry,Transaction * transaction)966 int HttpCache::CreateEntry(const std::string& key,
967                            scoped_refptr<ActiveEntry>* entry,
968                            Transaction* transaction) {
969   if (HasActiveEntry(key)) {
970     return ERR_CACHE_RACE;
971   }
972 
973   PendingOp* pending_op = GetPendingOp(key);
974   int rv =
975       CreateAndSetWorkItem(entry, transaction, WI_CREATE_ENTRY, pending_op);
976   if (rv != OK) {
977     return rv;
978   }
979 
980   disk_cache::EntryResult entry_result = disk_cache_->CreateEntry(
981       key, transaction->priority(),
982       base::BindOnce(&HttpCache::OnPendingCreationOpComplete, GetWeakPtr(),
983                      pending_op));
984   rv = entry_result.net_error();
985   if (rv == ERR_IO_PENDING) {
986     pending_op->callback_will_delete = true;
987     return ERR_IO_PENDING;
988   }
989 
990   pending_op->writer->ClearTransaction();
991   OnPendingCreationOpComplete(GetWeakPtr(), pending_op,
992                               std::move(entry_result));
993   return rv;
994 }
995 
AddTransactionToEntry(scoped_refptr<ActiveEntry> & entry,Transaction * transaction)996 int HttpCache::AddTransactionToEntry(scoped_refptr<ActiveEntry>& entry,
997                                      Transaction* transaction) {
998   DCHECK(entry);
999   DCHECK(entry->GetEntry());
1000   // Always add a new transaction to the queue to maintain FIFO order.
1001   entry->add_to_entry_queue().push_back(transaction);
1002   // Don't process the transaction if the lock timeout handling is being tested.
1003   if (!bypass_lock_for_test_) {
1004     ProcessQueuedTransactions(entry);
1005   }
1006   return ERR_IO_PENDING;
1007 }
1008 
DoneWithResponseHeaders(scoped_refptr<ActiveEntry> & entry,Transaction * transaction,bool is_partial)1009 int HttpCache::DoneWithResponseHeaders(scoped_refptr<ActiveEntry>& entry,
1010                                        Transaction* transaction,
1011                                        bool is_partial) {
1012   // If |transaction| is the current writer, do nothing. This can happen for
1013   // range requests since they can go back to headers phase after starting to
1014   // write.
1015   if (entry->HasWriters() && entry->writers()->HasTransaction(transaction)) {
1016     DCHECK(is_partial && entry->writers()->GetTransactionsCount() == 1);
1017     return OK;
1018   }
1019 
1020   DCHECK_EQ(entry->headers_transaction(), transaction);
1021 
1022   entry->ClearHeadersTransaction();
1023 
1024   // If transaction is responsible for writing the response body, then do not go
1025   // through done_headers_queue for performance benefit. (Also, in case of
1026   // writer transaction, the consumer sometimes depend on synchronous behaviour
1027   // e.g. while computing raw headers size. (crbug.com/711766))
1028   if ((transaction->mode() & Transaction::WRITE) && !entry->HasWriters() &&
1029       entry->readers().empty()) {
1030     entry->AddTransactionToWriters(
1031         transaction, CanTransactionJoinExistingWriters(transaction));
1032     ProcessQueuedTransactions(entry);
1033     return OK;
1034   }
1035 
1036   entry->done_headers_queue().push_back(transaction);
1037   ProcessQueuedTransactions(entry);
1038   return ERR_IO_PENDING;
1039 }
1040 
DoneWithEntry(scoped_refptr<ActiveEntry> & entry,Transaction * transaction,bool entry_is_complete,bool is_partial)1041 void HttpCache::DoneWithEntry(scoped_refptr<ActiveEntry>& entry,
1042                               Transaction* transaction,
1043                               bool entry_is_complete,
1044                               bool is_partial) {
1045   bool is_mode_read_only = transaction->mode() == Transaction::READ;
1046 
1047   if (!entry_is_complete && !is_mode_read_only && is_partial) {
1048     entry->GetEntry()->CancelSparseIO();
1049   }
1050 
1051   // Transaction is waiting in the done_headers_queue.
1052   auto it = base::ranges::find(entry->done_headers_queue(), transaction);
1053   if (it != entry->done_headers_queue().end()) {
1054     entry->done_headers_queue().erase(it);
1055 
1056     // Restart other transactions if this transaction could have written
1057     // response body.
1058     if (!entry_is_complete && !is_mode_read_only) {
1059       ProcessEntryFailure(entry.get());
1060     }
1061     return;
1062   }
1063 
1064   // Transaction is removed in the headers phase.
1065   if (transaction == entry->headers_transaction()) {
1066     entry->ClearHeadersTransaction();
1067 
1068     if (entry_is_complete || is_mode_read_only) {
1069       ProcessQueuedTransactions(entry);
1070     } else {
1071       // Restart other transactions if this transaction could have written
1072       // response body.
1073       ProcessEntryFailure(entry.get());
1074     }
1075     return;
1076   }
1077 
1078   // Transaction is removed in the writing phase.
1079   if (entry->HasWriters() && entry->writers()->HasTransaction(transaction)) {
1080     entry->writers()->RemoveTransaction(transaction,
1081                                         entry_is_complete /* success */);
1082     return;
1083   }
1084 
1085   // Transaction is reading from the entry.
1086   DCHECK(!entry->HasWriters());
1087   auto readers_it = entry->readers().find(transaction);
1088   DCHECK(readers_it != entry->readers().end());
1089   entry->readers().erase(readers_it);
1090   ProcessQueuedTransactions(entry);
1091 }
1092 
WritersDoomEntryRestartTransactions(ActiveEntry * entry)1093 void HttpCache::WritersDoomEntryRestartTransactions(ActiveEntry* entry) {
1094   DCHECK(!entry->writers()->IsEmpty());
1095   ProcessEntryFailure(entry);
1096 }
1097 
WritersDoneWritingToEntry(scoped_refptr<ActiveEntry> entry,bool success,bool should_keep_entry,TransactionSet make_readers)1098 void HttpCache::WritersDoneWritingToEntry(scoped_refptr<ActiveEntry> entry,
1099                                           bool success,
1100                                           bool should_keep_entry,
1101                                           TransactionSet make_readers) {
1102   // Impacts the queued transactions in one of the following ways:
1103   // - restart them but do not doom the entry since entry can be saved in
1104   // its truncated form.
1105   // - restart them and doom/destroy the entry since entry does not
1106   // have valid contents.
1107   // - let them continue by invoking their callback since entry is
1108   // successfully written.
1109   DCHECK(entry->HasWriters());
1110   DCHECK(entry->writers()->IsEmpty());
1111   DCHECK(success || make_readers.empty());
1112 
1113   if (!success && should_keep_entry) {
1114     // Restart already validated transactions so that they are able to read
1115     // the truncated status of the entry.
1116     entry->RestartHeadersPhaseTransactions();
1117     entry->ReleaseWriters();
1118     return;
1119   }
1120 
1121   if (success) {
1122     // Add any idle writers to readers.
1123     for (Transaction* reader : make_readers) {
1124       reader->WriteModeTransactionAboutToBecomeReader();
1125       entry->readers().insert(reader);
1126     }
1127     // Reset writers here so that WriteModeTransactionAboutToBecomeReader can
1128     // access the network transaction.
1129     entry->ReleaseWriters();
1130     ProcessQueuedTransactions(std::move(entry));
1131   } else {
1132     entry->ReleaseWriters();
1133     ProcessEntryFailure(entry.get());
1134   }
1135 }
1136 
DoomEntryValidationNoMatch(scoped_refptr<ActiveEntry> entry)1137 void HttpCache::DoomEntryValidationNoMatch(scoped_refptr<ActiveEntry> entry) {
1138   // Validating transaction received a non-matching response.
1139   DCHECK(entry->headers_transaction());
1140 
1141   entry->ClearHeadersTransaction();
1142 
1143   DoomActiveEntry(entry->GetEntry()->GetKey());
1144 
1145   // Restart only add_to_entry_queue transactions.
1146   // Post task here to avoid a race in creating the entry between |transaction|
1147   // and the add_to_entry_queue transactions. Reset the queued transaction's
1148   // cache pending state so that in case it's destructor is invoked, it's ok
1149   // for the transaction to not be found in this entry.
1150   for (net::HttpCache::Transaction* transaction : entry->add_to_entry_queue()) {
1151     transaction->ResetCachePendingState();
1152     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1153         FROM_HERE,
1154         base::BindOnce(transaction->cache_io_callback(), net::ERR_CACHE_RACE));
1155   }
1156   entry->add_to_entry_queue().clear();
1157 }
1158 
ProcessEntryFailure(ActiveEntry * entry)1159 void HttpCache::ProcessEntryFailure(ActiveEntry* entry) {
1160   // The writer failed to completely write the response to
1161   // the cache.
1162 
1163   if (entry->headers_transaction()) {
1164     entry->RestartHeadersTransaction();
1165   }
1166 
1167   TransactionList list = entry->TakeAllQueuedTransactions();
1168 
1169   DoomActiveEntry(entry->GetEntry()->GetKey());
1170 
1171   // ERR_CACHE_RACE causes the transaction to restart the whole process.
1172   for (Transaction* queued_transaction : list) {
1173     queued_transaction->cache_io_callback().Run(net::ERR_CACHE_RACE);
1174   }
1175 }
1176 
ProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry)1177 void HttpCache::ProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry) {
1178   // Multiple readers may finish with an entry at once, so we want to batch up
1179   // calls to OnProcessQueuedTransactions. This flag also tells us that we
1180   // should not delete the entry before OnProcessQueuedTransactions runs.
1181   if (entry->will_process_queued_transactions()) {
1182     return;
1183   }
1184 
1185   entry->set_will_process_queued_transactions(true);
1186 
1187   // Post a task instead of invoking the io callback of another transaction here
1188   // to avoid re-entrancy.
1189   base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1190       FROM_HERE, base::BindOnce(&HttpCache::OnProcessQueuedTransactions,
1191                                 GetWeakPtr(), std::move(entry)));
1192 }
1193 
ProcessAddToEntryQueue(scoped_refptr<ActiveEntry> entry)1194 void HttpCache::ProcessAddToEntryQueue(scoped_refptr<ActiveEntry> entry) {
1195   CHECK(!entry->add_to_entry_queue().empty());
1196   if (delay_add_transaction_to_entry_for_test_) {
1197     // Post a task to put the AddTransactionToEntry handling at the back of
1198     // the task queue. This allows other tasks (like network IO) to jump
1199     // ahead and simulate different callback ordering for testing.
1200     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1201         FROM_HERE, base::BindOnce(&HttpCache::ProcessAddToEntryQueueImpl,
1202                                   GetWeakPtr(), std::move(entry)));
1203   } else {
1204     entry->ProcessAddToEntryQueue();
1205   }
1206 }
1207 
ProcessAddToEntryQueueImpl(scoped_refptr<ActiveEntry> entry)1208 void HttpCache::ProcessAddToEntryQueueImpl(scoped_refptr<ActiveEntry> entry) {
1209   entry->ProcessAddToEntryQueue();
1210 }
1211 
CanTransactionJoinExistingWriters(Transaction * transaction)1212 HttpCache::ParallelWritingPattern HttpCache::CanTransactionJoinExistingWriters(
1213     Transaction* transaction) {
1214   if (transaction->method() != "GET") {
1215     return PARALLEL_WRITING_NOT_JOIN_METHOD_NOT_GET;
1216   }
1217   if (transaction->partial()) {
1218     return PARALLEL_WRITING_NOT_JOIN_RANGE;
1219   }
1220   if (transaction->mode() == Transaction::READ) {
1221     return PARALLEL_WRITING_NOT_JOIN_READ_ONLY;
1222   }
1223   if (transaction->GetResponseInfo()->headers &&
1224       transaction->GetResponseInfo()->headers->GetContentLength() >
1225           disk_cache_->MaxFileSize()) {
1226     return PARALLEL_WRITING_NOT_JOIN_TOO_BIG_FOR_CACHE;
1227   }
1228   return PARALLEL_WRITING_JOIN;
1229 }
1230 
ProcessDoneHeadersQueue(scoped_refptr<ActiveEntry> entry)1231 void HttpCache::ProcessDoneHeadersQueue(scoped_refptr<ActiveEntry> entry) {
1232   ParallelWritingPattern writers_pattern;
1233   DCHECK(!entry->HasWriters() ||
1234          entry->writers()->CanAddWriters(&writers_pattern));
1235   DCHECK(!entry->done_headers_queue().empty());
1236 
1237   Transaction* transaction = entry->done_headers_queue().front();
1238 
1239   ParallelWritingPattern parallel_writing_pattern =
1240       CanTransactionJoinExistingWriters(transaction);
1241   if (entry->IsWritingInProgress()) {
1242     if (parallel_writing_pattern != PARALLEL_WRITING_JOIN) {
1243       // TODO(shivanisha): Returning from here instead of checking the next
1244       // transaction in the queue because the FIFO order is maintained
1245       // throughout, until it becomes a reader or writer. May be at this point
1246       // the ordering is not important but that would be optimizing a rare
1247       // scenario where write mode transactions are insterspersed with read-only
1248       // transactions.
1249       return;
1250     }
1251     entry->AddTransactionToWriters(transaction, parallel_writing_pattern);
1252   } else {  // no writing in progress
1253     if (transaction->mode() & Transaction::WRITE) {
1254       if (transaction->partial()) {
1255         if (entry->readers().empty()) {
1256           entry->AddTransactionToWriters(transaction, parallel_writing_pattern);
1257         } else {
1258           return;
1259         }
1260       } else {
1261         // Add the transaction to readers since the response body should have
1262         // already been written. (If it was the first writer about to start
1263         // writing to the cache, it would have been added to writers in
1264         // DoneWithResponseHeaders, thus no writers here signify the response
1265         // was completely written).
1266         transaction->WriteModeTransactionAboutToBecomeReader();
1267         auto return_val = entry->readers().insert(transaction);
1268         DCHECK(return_val.second);
1269       }
1270     } else {  // mode READ
1271       auto return_val = entry->readers().insert(transaction);
1272       DCHECK(return_val.second);
1273     }
1274   }
1275 
1276   // Post another task to give a chance to more transactions to either join
1277   // readers or another transaction to start parallel validation.
1278   ProcessQueuedTransactions(entry);
1279 
1280   entry->done_headers_queue().erase(entry->done_headers_queue().begin());
1281   transaction->cache_io_callback().Run(OK);
1282 }
1283 
GetLoadStateForPendingTransaction(const Transaction * transaction)1284 LoadState HttpCache::GetLoadStateForPendingTransaction(
1285     const Transaction* transaction) {
1286   auto i = active_entries_.find(transaction->key());
1287   if (i == active_entries_.end()) {
1288     // If this is really a pending transaction, and it is not part of
1289     // active_entries_, we should be creating the backend or the entry.
1290     return LOAD_STATE_WAITING_FOR_CACHE;
1291   }
1292 
1293   Writers* writers = i->second->writers();
1294   return !writers ? LOAD_STATE_WAITING_FOR_CACHE : writers->GetLoadState();
1295 }
1296 
RemovePendingTransaction(Transaction * transaction)1297 void HttpCache::RemovePendingTransaction(Transaction* transaction) {
1298   auto i = active_entries_.find(transaction->key());
1299   bool found = false;
1300   if (i != active_entries_.end()) {
1301     found = i->second->RemovePendingTransaction(transaction);
1302   }
1303 
1304   if (found) {
1305     return;
1306   }
1307 
1308   if (building_backend_) {
1309     auto j = pending_ops_.find(std::string());
1310     if (j != pending_ops_.end()) {
1311       found = RemovePendingTransactionFromPendingOp(j->second, transaction);
1312     }
1313 
1314     if (found) {
1315       return;
1316     }
1317   }
1318 
1319   auto j = pending_ops_.find(transaction->key());
1320   if (j != pending_ops_.end()) {
1321     found = RemovePendingTransactionFromPendingOp(j->second, transaction);
1322   }
1323 
1324   if (found) {
1325     return;
1326   }
1327 
1328   for (auto k = doomed_entries_.begin(); k != doomed_entries_.end() && !found;
1329        ++k) {
1330     // TODO(ricea): Add unit test for this line.
1331     found = k->get().RemovePendingTransaction(transaction);
1332   }
1333 
1334   DCHECK(found) << "Pending transaction not found";
1335 }
1336 
RemovePendingTransactionFromPendingOp(PendingOp * pending_op,Transaction * transaction)1337 bool HttpCache::RemovePendingTransactionFromPendingOp(
1338     PendingOp* pending_op,
1339     Transaction* transaction) {
1340   if (pending_op->writer->Matches(transaction)) {
1341     pending_op->writer->ClearTransaction();
1342     pending_op->writer->ClearEntry();
1343     return true;
1344   }
1345   WorkItemList& pending_queue = pending_op->pending_queue;
1346 
1347   for (auto it = pending_queue.begin(); it != pending_queue.end(); ++it) {
1348     if ((*it)->Matches(transaction)) {
1349       pending_queue.erase(it);
1350       return true;
1351     }
1352   }
1353   return false;
1354 }
1355 
OnProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry)1356 void HttpCache::OnProcessQueuedTransactions(scoped_refptr<ActiveEntry> entry) {
1357   entry->set_will_process_queued_transactions(false);
1358 
1359   // Note that this function should only invoke one transaction's IO callback
1360   // since its possible for IO callbacks' consumers to destroy the cache/entry.
1361 
1362   if (entry->done_headers_queue().empty() &&
1363       entry->add_to_entry_queue().empty()) {
1364     return;
1365   }
1366 
1367   // To maintain FIFO order of transactions, done_headers_queue should be
1368   // checked for processing before add_to_entry_queue.
1369 
1370   // If another transaction is writing the response, let validated transactions
1371   // wait till the response is complete. If the response is not yet started, the
1372   // done_headers_queue transaction should start writing it.
1373   if (!entry->done_headers_queue().empty()) {
1374     ParallelWritingPattern unused_reason;
1375     if (!entry->writers() || entry->writers()->CanAddWriters(&unused_reason)) {
1376       ProcessDoneHeadersQueue(entry);
1377       return;
1378     }
1379   }
1380 
1381   if (!entry->add_to_entry_queue().empty()) {
1382     ProcessAddToEntryQueue(std::move(entry));
1383   }
1384 }
1385 
OnIOComplete(int result,PendingOp * pending_op)1386 void HttpCache::OnIOComplete(int result, PendingOp* pending_op) {
1387   WorkItemOperation op = pending_op->writer->operation();
1388 
1389   // Completing the creation of the backend is simpler than the other cases.
1390   if (op == WI_CREATE_BACKEND) {
1391     return OnBackendCreated(result, pending_op);
1392   }
1393 
1394   std::unique_ptr<WorkItem> item = std::move(pending_op->writer);
1395   bool try_restart_requests = false;
1396 
1397   scoped_refptr<ActiveEntry> entry;
1398   std::string key;
1399   if (result == OK) {
1400     if (op == WI_DOOM_ENTRY) {
1401       // Anything after a Doom has to be restarted.
1402       try_restart_requests = true;
1403     } else if (item->IsValid()) {
1404       DCHECK(pending_op->entry);
1405       key = pending_op->entry->GetKey();
1406       entry = ActivateEntry(pending_op->entry, pending_op->entry_opened);
1407     } else {
1408       // The writer transaction is gone.
1409       if (!pending_op->entry_opened) {
1410         pending_op->entry->Doom();
1411       }
1412 
1413       pending_op->entry->Close();
1414       pending_op->entry = nullptr;
1415       try_restart_requests = true;
1416     }
1417   }
1418 
1419   // We are about to notify a bunch of transactions, and they may decide to
1420   // re-issue a request (or send a different one). If we don't delete
1421   // pending_op, the new request will be appended to the end of the list, and
1422   // we'll see it again from this point before it has a chance to complete (and
1423   // we'll be messing out the request order). The down side is that if for some
1424   // reason notifying request A ends up cancelling request B (for the same key),
1425   // we won't find request B anywhere (because it would be in a local variable
1426   // here) and that's bad. If there is a chance for that to happen, we'll have
1427   // to move the callback used to be a CancelableOnceCallback. By the way, for
1428   // this to happen the action (to cancel B) has to be synchronous to the
1429   // notification for request A.
1430   WorkItemList pending_items = std::move(pending_op->pending_queue);
1431   DeletePendingOp(pending_op);
1432 
1433   item->NotifyTransaction(result, entry);
1434 
1435   while (!pending_items.empty()) {
1436     item = std::move(pending_items.front());
1437     pending_items.pop_front();
1438 
1439     if (item->operation() == WI_DOOM_ENTRY) {
1440       // A queued doom request is always a race.
1441       try_restart_requests = true;
1442     } else if (result == OK) {
1443       entry = GetActiveEntry(key);
1444       if (!entry) {
1445         try_restart_requests = true;
1446       }
1447     }
1448 
1449     if (try_restart_requests) {
1450       item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1451       continue;
1452     }
1453     // At this point item->operation() is anything except Doom.
1454     if (item->operation() == WI_CREATE_ENTRY) {
1455       if (result == OK) {
1456         // Successful OpenOrCreate, Open, or Create followed by a Create.
1457         item->NotifyTransaction(ERR_CACHE_CREATE_FAILURE, nullptr);
1458       } else {
1459         if (op != WI_CREATE_ENTRY && op != WI_OPEN_OR_CREATE_ENTRY) {
1460           // Failed Open or Doom followed by a Create.
1461           item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1462           try_restart_requests = true;
1463         } else {
1464           item->NotifyTransaction(result, entry);
1465         }
1466       }
1467     }
1468     // item->operation() is OpenOrCreate or Open
1469     else if (item->operation() == WI_OPEN_OR_CREATE_ENTRY) {
1470       if ((op == WI_OPEN_ENTRY || op == WI_CREATE_ENTRY) && result != OK) {
1471         // Failed Open or Create followed by an OpenOrCreate.
1472         item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1473         try_restart_requests = true;
1474       } else {
1475         item->NotifyTransaction(result, entry);
1476       }
1477     }
1478     // item->operation() is Open.
1479     else {
1480       if (op == WI_CREATE_ENTRY && result != OK) {
1481         // Failed Create followed by an Open.
1482         item->NotifyTransaction(ERR_CACHE_RACE, nullptr);
1483         try_restart_requests = true;
1484       } else {
1485         item->NotifyTransaction(result, entry);
1486       }
1487     }
1488   }
1489 }
1490 
1491 // static
OnPendingOpComplete(base::WeakPtr<HttpCache> cache,PendingOp * pending_op,int rv)1492 void HttpCache::OnPendingOpComplete(base::WeakPtr<HttpCache> cache,
1493                                     PendingOp* pending_op,
1494                                     int rv) {
1495   if (cache.get()) {
1496     pending_op->callback_will_delete = false;
1497     cache->OnIOComplete(rv, pending_op);
1498   } else {
1499     // The callback was cancelled so we should delete the pending_op that
1500     // was used with this callback.
1501     delete pending_op;
1502   }
1503 }
1504 
1505 // static
OnPendingCreationOpComplete(base::WeakPtr<HttpCache> cache,PendingOp * pending_op,disk_cache::EntryResult result)1506 void HttpCache::OnPendingCreationOpComplete(base::WeakPtr<HttpCache> cache,
1507                                             PendingOp* pending_op,
1508                                             disk_cache::EntryResult result) {
1509   if (!cache.get()) {
1510     // The callback was cancelled so we should delete the pending_op that
1511     // was used with this callback. If |result| contains a fresh entry
1512     // it will close it automatically, since we don't release it here.
1513     delete pending_op;
1514     return;
1515   }
1516 
1517   int rv = result.net_error();
1518   pending_op->entry_opened = result.opened();
1519   pending_op->entry = result.ReleaseEntry();
1520   pending_op->callback_will_delete = false;
1521   cache->OnIOComplete(rv, pending_op);
1522 }
1523 
1524 // static
OnPendingBackendCreationOpComplete(base::WeakPtr<HttpCache> cache,PendingOp * pending_op,disk_cache::BackendResult result)1525 void HttpCache::OnPendingBackendCreationOpComplete(
1526     base::WeakPtr<HttpCache> cache,
1527     PendingOp* pending_op,
1528     disk_cache::BackendResult result) {
1529   if (!cache.get()) {
1530     // The callback was cancelled so we should delete the pending_op that
1531     // was used with this callback. If `result` contains a cache backend,
1532     // it will be destroyed with it.
1533     delete pending_op;
1534     return;
1535   }
1536 
1537   int rv = result.net_error;
1538   pending_op->backend = std::move(result.backend);
1539   pending_op->callback_will_delete = false;
1540   cache->OnIOComplete(rv, pending_op);
1541 }
1542 
OnBackendCreated(int result,PendingOp * pending_op)1543 void HttpCache::OnBackendCreated(int result, PendingOp* pending_op) {
1544   std::unique_ptr<WorkItem> item = std::move(pending_op->writer);
1545   WorkItemOperation op = item->operation();
1546   DCHECK_EQ(WI_CREATE_BACKEND, op);
1547 
1548   if (backend_factory_.get()) {
1549     // We may end up calling OnBackendCreated multiple times if we have pending
1550     // work items. The first call saves the backend and releases the factory,
1551     // and the last call clears building_backend_.
1552     backend_factory_.reset();  // Reclaim memory.
1553     if (result == OK) {
1554       disk_cache_ = std::move(pending_op->backend);
1555       UMA_HISTOGRAM_MEMORY_KB("HttpCache.MaxFileSizeOnInit",
1556                               disk_cache_->MaxFileSize() / 1024);
1557     }
1558   }
1559 
1560   if (!pending_op->pending_queue.empty()) {
1561     std::unique_ptr<WorkItem> pending_item =
1562         std::move(pending_op->pending_queue.front());
1563     pending_op->pending_queue.pop_front();
1564     DCHECK_EQ(WI_CREATE_BACKEND, pending_item->operation());
1565 
1566     // We want to process a single callback at a time, because the cache may
1567     // go away from the callback.
1568     pending_op->writer = std::move(pending_item);
1569 
1570     base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
1571         FROM_HERE, base::BindOnce(&HttpCache::OnBackendCreated, GetWeakPtr(),
1572                                   result, pending_op));
1573   } else {
1574     building_backend_ = false;
1575     DeletePendingOp(pending_op);
1576   }
1577 
1578   // The cache may be gone when we return from the callback.
1579   if (!item->DoCallback(result)) {
1580     item->NotifyTransaction(result, nullptr);
1581   }
1582 }
1583 
1584 }  // namespace net
1585