blob: a4b47dc1b8dc743f906dd30aee5080f238e28618 [file] [log] [blame]
kenoss1b8ebc22025-04-04 00:11:351// Copyright 2025 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 "content/browser/preloading/prefetch/prefetch_scheduler.h"
6
7#include "base/auto_reset.h"
8#include "base/check_is_test.h"
9#include "base/types/pass_key.h"
10#include "content/browser/preloading/prefetch/prefetch_container.h"
11#include "content/browser/preloading/prefetch/prefetch_document_manager.h"
12#include "content/browser/preloading/prefetch/prefetch_features.h"
Hiroshige Hayashizakif55280f62025-08-19 17:23:2013#include "content/browser/preloading/prefetch/prefetch_request.h"
kenoss1b8ebc22025-04-04 00:11:3514#include "content/browser/preloading/prefetch/prefetch_service.h"
kenoss1eb925f2025-04-04 05:49:5615#include "content/browser/preloading/prerender/prerender_features.h"
Hiroshige Hayashizaki583b4962025-08-19 18:58:2616#include "content/public/browser/prefetch_priority.h"
Taiyo Mizuhashid13f8ca2025-06-23 13:29:0217#include "content/public/common/content_features.h"
kenoss1b8ebc22025-04-04 00:11:3518
19namespace content {
20
21namespace {
22
23size_t GetActiveSetSizeLimitForBase() {
24 // TODO(crbug.com/406403063): Update the limit for base.
25
26 if (base::FeatureList::IsEnabled(features::kPrefetchSchedulerTesting)) {
27 return features::kPrefetchSchedulerTestingActiveSetSizeLimitForBase.Get();
28 }
29
Taiyo Mizuhashi98e3dea2025-06-23 18:32:5830 if (base::FeatureList::IsEnabled(
31 features::kPrefetchMultipleActiveSetSizeLimitForBase)) {
32 return features::kPrefetchMultipleActiveSetSizeLimitForBaseValue.Get();
33 }
34
kenoss1b8ebc22025-04-04 00:11:3535 return 1;
36}
37
38size_t GetActiveSetSizeLimitForBurst() {
kenoss1b8ebc22025-04-04 00:11:3539 if (base::FeatureList::IsEnabled(features::kPrefetchSchedulerTesting)) {
40 return features::kPrefetchSchedulerTestingActiveSetSizeLimitForBurst.Get();
41 }
42
kenoss1eb925f2025-04-04 05:49:5643 // Before prefetch/prerender integration (i.e.
44 // `Prerender2FallbackPrefetchSpecRules` is disabled), prerender ran without
45 // prefetch So, it was not blocked by prefetch queue. Allow
46 // prefetch-ahead-of-prerender to run independently of the ordinal prefetch
47 // queue so that prerendering is not blocked by queued prefetch requests.
48 //
49 // Note that prerenders are run sequentially. So, +1 is enough.
50 if (features::kPrerender2FallbackPrefetchSchedulerPolicy.Get() ==
51 features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst) {
52 return GetActiveSetSizeLimitForBase() + 1;
53 }
54
Taiyo Mizuhashid13f8ca2025-06-23 13:29:0255 if (base::FeatureList::IsEnabled(
56 features::kWebViewPrefetchHighestPrefetchPriority)) {
57 return features::kWebViewPrefetchHighestPrefetchPriorityBurstLimit.Get();
58 }
59
kenoss1b8ebc22025-04-04 00:11:3560 // No additional room for burst.
61 return GetActiveSetSizeLimitForBase();
62}
63
Taiyo Mizuhashid7c856992025-06-23 08:56:0264PrefetchSchedulerPriority CalculatePriorityImpl(
kenoss1b8ebc22025-04-04 00:11:3565 const PrefetchContainer& prefetch_container) {
Hiroshige Hayashizaki8b59d9082025-08-19 18:58:0466 if (prefetch_container.request().priority().has_value()) {
67 switch (prefetch_container.request().priority().value()) {
Taiyo Mizuhashi53da92d2025-06-23 08:56:1568 case PrefetchPriority::kLow:
69 case PrefetchPriority::kMedium:
70 case PrefetchPriority::kHigh:
71 return PrefetchSchedulerPriority::kBase;
72 case PrefetchPriority::kHighest:
73 return PrefetchSchedulerPriority::kBurstForPrefetchPriority;
74 }
75 }
76
kenoss1eb925f2025-04-04 05:49:5677 // Burst/prioritize if ahead of prerender.
Taiyo Mizuhashi53da92d2025-06-23 08:56:1578 // TODO(crbug.com/426404355): Migrate to use `PrefetchPriority`.
kenoss1eb925f2025-04-04 05:49:5679 if (prefetch_container.IsLikelyAheadOfPrerender()) {
80 switch (features::kPrerender2FallbackPrefetchSchedulerPolicy.Get()) {
81 case features::Prerender2FallbackPrefetchSchedulerPolicy::kNotUse:
82 break;
83 case features::Prerender2FallbackPrefetchSchedulerPolicy::kPrioritize:
Taiyo Mizuhashid7c856992025-06-23 08:56:0284 return PrefetchSchedulerPriority::kHighAheadOfPrerender;
kenoss1eb925f2025-04-04 05:49:5685 case features::Prerender2FallbackPrefetchSchedulerPolicy::kBurst:
Taiyo Mizuhashid7c856992025-06-23 08:56:0286 return PrefetchSchedulerPriority::kBurstAheadOfPrerender;
kenoss1eb925f2025-04-04 05:49:5687 }
88 }
89
Taiyo Mizuhashid7c856992025-06-23 08:56:0290 return PrefetchSchedulerPriority::kBase;
kenoss1b8ebc22025-04-04 00:11:3591}
92
93bool IsReadyToStartPrefetch(const PrefetchQueue::Item& item) {
94 // Keep this method as similar as much as possible to a lambda in
95 // `PrefetchService::PopNextPrefetchContainer()`.
96 //
97 // TODO(crbug.com/406754449): Remove this comment.
98
99 // `prefetch_container` must be valid. It will be ensured by `PrefetchService`
100 // in the future.
101 //
102 // Return true and let it handle `PrefetchScheduler::Progress()`.
103 //
104 // TODO(crbug.com/400761083): Use `CHECK`.
105 if (!item.prefetch_container) {
106 return true;
107 }
108
Hiroshige Hayashizakif55280f62025-08-19 17:23:20109 auto* renderer_initiator_info =
110 item.prefetch_container->request().GetRendererInitiatorInfo();
111 if (!renderer_initiator_info) {
kenoss1b8ebc22025-04-04 00:11:35112 // TODO(crbug.com/40946257): Revisit the resource limits and
113 // conditions for starting browser-initiated prefetch.
114 return true;
115 }
116
117 auto* prefetch_document_manager =
Hiroshige Hayashizakif55280f62025-08-19 17:23:20118 renderer_initiator_info->prefetch_document_manager();
kenoss1b8ebc22025-04-04 00:11:35119 // If there is no manager in renderer-initiated prefetch (can happen
120 // only in tests), just bypass the check.
121 if (!prefetch_document_manager) {
122 CHECK_IS_TEST();
123 return true;
124 }
125
126 // Eviction wil be handled in `PrefetchScheduler::ProgressOne()`.
127 return std::get<0>(
128 prefetch_document_manager->CanPrefetchNow(item.prefetch_container.get()));
129}
130
131} // namespace
132
133PrefetchQueue::Item::Item(base::WeakPtr<PrefetchContainer> prefetch_container,
Taiyo Mizuhashid7c856992025-06-23 08:56:02134 PrefetchSchedulerPriority priority)
kenoss1b8ebc22025-04-04 00:11:35135 : prefetch_container(std::move(prefetch_container)), priority(priority) {}
136
137PrefetchQueue::Item::Item(const PrefetchQueue::Item&& other)
138 : prefetch_container(std::move(other.prefetch_container)),
139 priority(other.priority) {}
140
141PrefetchQueue::Item& PrefetchQueue::Item::operator=(
142 const PrefetchQueue::Item&& other) {
143 prefetch_container = std::move(other.prefetch_container);
144 priority = other.priority;
145
146 return *this;
147}
148
149PrefetchQueue::Item::~Item() = default;
150
151PrefetchQueue::PrefetchQueue() = default;
152
153PrefetchQueue::~PrefetchQueue() = default;
154
155void PrefetchQueue::Push(base::WeakPtr<PrefetchContainer> prefetch_container,
Taiyo Mizuhashid7c856992025-06-23 08:56:02156 PrefetchSchedulerPriority priority) {
kenoss1b8ebc22025-04-04 00:11:35157 CHECK(prefetch_container);
158 // Postcondition: Pushing registered one is not allowed.
159 CHECK(!Remove(prefetch_container));
160
161 auto mid = std::partition_point(queue_.begin(), queue_.end(),
162 [priority](PrefetchQueue::Item& item) {
163 return item.priority >= priority;
164 });
165 queue_.insert(mid,
166 PrefetchQueue::Item(std::move(prefetch_container), priority));
167}
168
169bool PrefetchQueue::Remove(
170 base::WeakPtr<PrefetchContainer> prefetch_container) {
171 for (auto it = queue_.cbegin(); it != queue_.cend(); ++it) {
172 if (it->prefetch_container.get() == prefetch_container.get()) {
173 queue_.erase(it);
174 return true;
175 }
176 }
177
178 return false;
179}
180
181bool PrefetchQueue::MaybeUpdatePriority(PrefetchContainer& prefetch_container,
Taiyo Mizuhashid7c856992025-06-23 08:56:02182 PrefetchSchedulerPriority priority) {
kenoss1b8ebc22025-04-04 00:11:35183 for (auto it = queue_.cbegin(); it != queue_.cend(); ++it) {
184 if (it->prefetch_container.get() == &prefetch_container) {
185 if (it->priority != priority) {
186 queue_.erase(it);
187 Push(prefetch_container.GetWeakPtr(), priority);
188 return true;
189 } else {
190 return false;
191 }
192 }
193 }
194
195 return false;
196}
197
198PrefetchScheduler::PrefetchScheduler(PrefetchService* prefetch_service)
199 : prefetch_service_(prefetch_service) {}
200
201PrefetchScheduler::~PrefetchScheduler() = default;
202
203bool PrefetchScheduler::IsInActiveSet(
204 const PrefetchContainer& prefetch_container) {
205 for (auto& active_prefetch_container : active_set_) {
206 if (&prefetch_container == active_prefetch_container.get()) {
207 return true;
208 }
209 }
210
211 return false;
212}
213
Taiyo Mizuhashid7c856992025-06-23 08:56:02214PrefetchSchedulerPriority PrefetchScheduler::CalculatePriority(
kenoss1b8ebc22025-04-04 00:11:35215 const PrefetchContainer& prefetch_container) {
216 if (calculate_priority_for_test_) {
217 return calculate_priority_for_test_.Run(prefetch_container);
218 }
219
220 return CalculatePriorityImpl(prefetch_container);
221}
222
kenoss0a567362025-05-16 11:18:55223void PrefetchScheduler::PushAndProgress(PrefetchContainer& prefetch_container) {
224 // Precondition: Pushing already registered one is not allowed.
225 for (auto& it : active_set_) {
226 if (it.get() == &prefetch_container) {
227 NOTREACHED();
228 }
229 }
230
Taiyo Mizuhashid7c856992025-06-23 08:56:02231 PrefetchSchedulerPriority priority = CalculatePriority(prefetch_container);
kenoss0a567362025-05-16 11:18:55232 queue_.Push(prefetch_container.GetWeakPtr(), priority);
233
234 Progress();
235}
236
kenoss1b8ebc22025-04-04 00:11:35237void PrefetchScheduler::PushAndProgressAsync(
238 PrefetchContainer& prefetch_container) {
239 // Precondition: Pushing already registered one is not allowed.
240 for (auto& it : active_set_) {
241 if (it.get() == &prefetch_container) {
242 NOTREACHED();
243 }
244 }
245
Taiyo Mizuhashid7c856992025-06-23 08:56:02246 PrefetchSchedulerPriority priority = CalculatePriority(prefetch_container);
kenoss1b8ebc22025-04-04 00:11:35247 queue_.Push(prefetch_container.GetWeakPtr(), priority);
248
249 ProgressAsync();
250}
251
252void PrefetchScheduler::RemoveAndProgressAsync(
kenosse8d8bbb52025-05-13 12:35:04253 PrefetchContainer& prefetch_container,
254 bool should_progress) {
kenoss1b8ebc22025-04-04 00:11:35255 [&]() {
256 for (auto it = active_set_.cbegin(); it != active_set_.cend(); ++it) {
257 if (it->get() == &prefetch_container) {
258 active_set_.erase(it);
259 return;
260 }
261 }
262
263 queue_.Remove(prefetch_container.GetWeakPtr());
264 }();
265
kenosse8d8bbb52025-05-13 12:35:04266 if (!should_progress) {
267 return;
268 }
269
kenoss1b8ebc22025-04-04 00:11:35270 // This method can be called in `PrefetechService::EvictPrefetch()` called in
271 // `ProcessOne()`. Don't call `ProcessAsync()` to prevent infinite loop in
272 // that case.
273 if (!in_eviction_) {
274 ProgressAsync();
275 }
276}
277
278void PrefetchScheduler::NotifyAttributeMightChangedAndProgressAsync(
kenosse8d8bbb52025-05-13 12:35:04279 PrefetchContainer& prefetch_container,
280 bool should_progress) {
281 if (!should_progress) {
282 return;
283 }
284
kenoss1b8ebc22025-04-04 00:11:35285 const bool is_changed = queue_.MaybeUpdatePriority(
286 prefetch_container, CalculatePriority(prefetch_container));
287 if (is_changed) {
288 ProgressAsync();
289 }
290}
291
292void PrefetchScheduler::ProgressAsync() {
293 if (is_progress_scheduled_) {
294 return;
295 }
296 is_progress_scheduled_ = true;
297
298 base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
299 FROM_HERE, base::BindOnce(&PrefetchScheduler::Progress,
300 weak_method_factory_.GetWeakPtr()));
301}
302
303void PrefetchScheduler::Progress() {
304#if DCHECK_IS_ON()
305 // Asserts that reentrancy doesn't happen.
306 CHECK(!progress_reentrancy_guard_);
307 base::AutoReset guard(&progress_reentrancy_guard_, true);
308#endif
309
310 // Note that this doesn't correspond to the update in `ProgressAsync()` in 1:1
311 // and there is a case updating `false` to `false` as this method can be
312 // called from `PrefetchService` directly.
313 is_progress_scheduled_ = false;
314
315 // #algorithm
316 //
317 // 1. Start prefetches with burst priority with limit for burst.
318 //
319 // If the size of active set is < `GetActiveSetSizeLimitForBurst()`, pop
320 // `PrefetchContainer` with priority >= `kBurstThreshold` that can be
321 // started and start it. Continue it until active set size reaches the
322 // limit or queue becomes empty.
323 //
324 // 2. Start prefetches with limit.
325 //
326 // If the size of active set is < `GetActiveSetSizeLimitForBase()`, pop
327 // `PrefetchContainer` (with no priority condition) that can be
328 // started and start it. Continue it until active set size reaches the
329 // limit or queue becomes empty.
330 //
331 // TODO(crbug.com/406403063): Consider not to limit prefetches with burst
332 // priority. See
333 // https://chromium-review.googlesource.com/c/chromium/src/+/6402914/comment/8b5c845f_0b7f6f7e/
334
Taiyo Mizuhashid7c856992025-06-23 08:56:02335 auto internal = [&](PrefetchSchedulerPriority threshold_priority,
kenoss1b8ebc22025-04-04 00:11:35336 size_t active_limit) {
337 // Invariant: `active_set_.size() == 0 && there is a ready prefetch` is
338 // false. I.e. doesn't stuck.
339 while (active_set_.size() < active_limit) {
340 std::optional<PrefetchQueue::Item> item =
341 queue_.Pop(IsReadyToStartPrefetch, threshold_priority);
342 if (!item.has_value()) {
343 break;
344 }
345
346 base::WeakPtr<PrefetchContainer> prefetch_container =
347 item.value().prefetch_container;
348 // `prefetch_container` must be valid. It will be ensured by
349 // `PrefetchService` in the future.
350 //
351 // TODO(crbug.com/400761083): Use `CHECK`.
352 if (!prefetch_container) {
353 continue;
354 }
355
356 // This call calls a method of `PrefetchService` and can incur methods of
357 // `PrefetchScheduler`. It is safe as we don't hold iterators at this
358 // timing.
359 ProgressOne(std::move(prefetch_container));
360 }
361 };
362
Taiyo Mizuhashid7c856992025-06-23 08:56:02363 internal(PrefetchSchedulerPriority::kBurstThreshold,
364 GetActiveSetSizeLimitForBurst());
365 internal(PrefetchSchedulerPriority::kBase, GetActiveSetSizeLimitForBase());
kenoss1b8ebc22025-04-04 00:11:35366}
367
368void PrefetchScheduler::ProgressOne(
369 base::WeakPtr<PrefetchContainer> prefetch_container) {
370 CHECK(prefetch_container);
371
372 // Evict if needed.
373 [&]() {
Hiroshige Hayashizakif55280f62025-08-19 17:23:20374 auto* renderer_initiator_info =
375 prefetch_container->request().GetRendererInitiatorInfo();
376 if (!renderer_initiator_info) {
377 return;
378 }
379
kenoss1b8ebc22025-04-04 00:11:35380 auto* prefetch_document_manager =
Hiroshige Hayashizakif55280f62025-08-19 17:23:20381 renderer_initiator_info->prefetch_document_manager();
kenoss1b8ebc22025-04-04 00:11:35382 if (!prefetch_document_manager) {
383 return;
384 }
385
386 base::WeakPtr<PrefetchContainer> prefetch_to_evict = std::get<1>(
387 prefetch_document_manager->CanPrefetchNow(prefetch_container.get()));
388 if (!prefetch_to_evict) {
389 return;
390 }
391
392 {
393 base::AutoReset<bool> guard{&in_eviction_, true};
394 prefetch_service_->EvictPrefetch(base::PassKey<PrefetchScheduler>(),
395 prefetch_to_evict);
396 }
397 }();
398
399 const bool is_started = prefetch_service_->StartSinglePrefetch(
400 base::PassKey<PrefetchScheduler>(), prefetch_container);
401 if (is_started) {
402 active_set_.push_back(prefetch_container);
403 }
404}
405
406void PrefetchScheduler::SetCalculatePriorityForTesting(
Taiyo Mizuhashid7c856992025-06-23 08:56:02407 base::RepeatingCallback<PrefetchSchedulerPriority(const PrefetchContainer&)>
kenoss1b8ebc22025-04-04 00:11:35408 callback) {
409 calculate_priority_for_test_ = std::move(callback);
410}
411
412} // namespace content