1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/prefetch_cache.h"
#include <ostream>
#include "base/check.h"
#include "base/check_op.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/types/pass_key.h"
#include "net/base/network_isolation_key.h"
#include "services/network/prefetch_url_loader_client.h"
#include "services/network/public/cpp/features.h"
namespace network {
namespace {
size_t GetMaxSize() {
// How many prefetches should be cached before old ones are evicted. This
// provides rough control over the overall memory used by prefetches.
return static_cast<size_t>(std::max(
base::GetFieldTrialParamByFeatureAsInt(features::kNetworkContextPrefetch,
/*name=*/"max_loaders",
/*default_value=*/10),
1));
}
base::TimeDelta GetEraseGraceTime() {
// When "NetworkContextPrefetchUseMatches" is disabled, how long to leave a
// matched cache entry alive before deleting it. This corresponds to the
// expected maximum time it will take for a request to reach the HttpCache
// once it has been initiated. Since it may be delayed by the
// ResourceScheduler, give the delay is quite large.
//
// Why not shorter: the request from the render process may be delayed by the
// ResourceScheduler.
//
// Why not longer: If the prefetch has not yet received response headers, it
// has an exclusive cache lock. The real request from the render process
// cannot proceed until the cache lock is released. If the response turns out
// to be uncacheable, then this time is pure waste.
return std::max(base::Seconds(0),
base::GetFieldTrialParamByFeatureAsTimeDelta(
features::kNetworkContextPrefetch,
/*name=*/"erase_grace_time",
/*default_value=*/base::Seconds(1)));
}
} // namespace
PrefetchCache::PrefetchCache()
: max_size_(GetMaxSize()), erase_grace_time_(GetEraseGraceTime()) {}
PrefetchCache::~PrefetchCache() = default;
PrefetchURLLoaderClient* PrefetchCache::Emplace(
const ResourceRequest& request) {
if (!request.trusted_params.has_value()) {
DLOG(WARNING)
<< "NetworkContext::Emplace() was called with a request with no "
"NetworkIsolationKey. This is not going to work.";
return nullptr;
}
const net::NetworkIsolationKey& nik =
request.trusted_params->isolation_info.network_isolation_key();
if (nik.IsTransient()) {
DLOG(WARNING) << "NetworkContext::Emplace() was called with a request with "
"a transient NetworkIsolationKey. This won't match "
"anything, so ignoring.";
return nullptr;
}
if (!request.url.SchemeIsHTTPOrHTTPS()) {
DLOG(WARNING) << "NetworkContext::Emplace() was called with a scheme that "
"is not http or https. This is not going to work.";
return nullptr;
}
if (map_.contains(KeyType(nik, request.url))) {
return nullptr;
}
while (map_.size() >= max_size_) {
EraseOldest();
}
auto [it, insert_ok] =
client_storage_.insert(std::make_unique<PrefetchURLLoaderClient>(
base::PassKey<PrefetchCache>(), nik, request,
/*expiry_time=*/base::TimeTicks::Now() + kMaxAge, this));
CHECK(insert_ok);
auto* client = it->get();
list_.Append(client);
auto [_, is_not_duplicated] = map_.emplace(
KeyType(client->network_isolation_key(), client->url()), client);
CHECK(is_not_duplicated);
if (!expiry_timer_.IsRunning()) {
StartTimer();
}
return client;
}
PrefetchURLLoaderClient* PrefetchCache::Lookup(
const net::NetworkIsolationKey& nik,
const GURL& url) {
const auto it = FindInMap(nik, url);
return it == map_.end() ? nullptr : it->second;
}
void PrefetchCache::Consume(PrefetchURLLoaderClient* client) {
const bool was_oldest = client == list_.head();
RemoveFromCache(client);
if (was_oldest) {
if (list_.empty()) {
expiry_timer_.Stop();
} else {
// Automatically resets the timer.
StartTimer();
}
}
}
void PrefetchCache::Erase(PrefetchURLLoaderClient* client) {
const auto it = FindInMap(client->network_isolation_key(), client->url());
if (it != map_.end()) {
CHECK_EQ(it->second, client);
map_.erase(it);
client->RemoveFromList();
}
EraseFromStorage(client);
}
void PrefetchCache::DelayedErase(PrefetchURLLoaderClient* client) {
const auto now = base::TimeTicks::Now();
PendingErasure pending_erasure = {client->network_isolation_key(),
client->url(), now + erase_grace_time_};
CHECK(Lookup(pending_erasure.nik, pending_erasure.url));
delayed_erase_queue_.push(std::move(pending_erasure));
SchedulePendingErases(now);
}
void PrefetchCache::OnTimer() {
auto now = base::TimeTicks::Now();
while (!list_.empty() &&
list_.head()->value()->expiry_time() <= now + kExpirySlack) {
EraseOldest();
}
if (!list_.empty()) {
StartTimer(now);
}
}
void PrefetchCache::DoDelayedErases() {
const auto now = base::TimeTicks::Now();
while (!delayed_erase_queue_.empty() &&
delayed_erase_queue_.front().erase_time <= now) {
PendingErasure pending = std::move(delayed_erase_queue_.front());
delayed_erase_queue_.pop();
PrefetchURLLoaderClient* to_erase = Lookup(pending.nik, pending.url);
if (to_erase) {
RemoveFromCache(to_erase);
EraseFromStorage(to_erase);
}
}
if (!delayed_erase_queue_.empty()) {
SchedulePendingErases(now);
}
}
void PrefetchCache::EraseOldest() {
CHECK(!list_.empty());
PrefetchURLLoaderClient* oldest = list_.head()->value();
RemoveFromCache(oldest);
// Make sure we actually removed the right one.
CHECK_NE(oldest, list_.head());
EraseFromStorage(oldest);
}
void PrefetchCache::RemoveFromCache(PrefetchURLLoaderClient* client) {
const auto it = FindInMap(client->network_isolation_key(), client->url());
CHECK(it != map_.end());
CHECK_EQ(it->second, client);
map_.erase(it);
client->RemoveFromList();
}
void PrefetchCache::EraseFromStorage(PrefetchURLLoaderClient* client) {
// In C++20, std::set::erase() doesn't support transparent comparisons, so
// it's necessary to use find() first.
auto it = client_storage_.find(client);
CHECK(it != client_storage_.end());
client_storage_.erase(it);
}
void PrefetchCache::StartTimer(base::TimeTicks now) {
CHECK(!list_.empty());
auto next_expiry = list_.head()->value()->expiry_time();
// It's safe to use base::Unretained() as destroying `this` will destroy
// `expiry_timer_`, preventing the callback being called.
expiry_timer_.Start(
FROM_HERE, std::max(base::Seconds(0), next_expiry - now),
base::BindOnce(&PrefetchCache::OnTimer, base::Unretained(this)));
}
void PrefetchCache::SchedulePendingErases(base::TimeTicks now) {
CHECK(!delayed_erase_queue_.empty());
const auto next_expiry = delayed_erase_queue_.front().erase_time;
const auto delay = std::max(base::Seconds(0), next_expiry - now);
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&PrefetchCache::DoDelayedErases,
weak_factory_.GetWeakPtr()),
delay);
}
PrefetchCache::MapType::iterator PrefetchCache::FindInMap(
const net::NetworkIsolationKey& nik,
const GURL& url) {
return map_.find(KeyType(nik, url));
}
} // namespace network
|