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