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