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