blob: b8d06bf5c1d8ea915eb2ca037cc5382b394daf3e [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SCHEDULER_H_
#define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SCHEDULER_H_
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "content/common/content_export.h"
namespace content {
class PrefetchContainer;
class PrefetchService;
// Priority of `PrefetchContainer`
//
// - Larger values take priority.
// - If `PrefetchContainer` has a priority larger than `kBurstThreshold`, it
// will be executed with additional capacity. For more details, see the
// comment #algorithm in `PrefetchScheduler::Progress()`.
// - Note that priority should be explicitly re-calculated if related attributes
// of `PrefetchContainer` are changed. See
// `PrefetchScheduler::CalculatePriority()`, the call sites, and
// `PrefetchScheduler::NotifyAttributeMightChangedAndProgressAsync()`.
//
// Maintenance policy:
//
// - Put a priority per reason. Think twice before mixing multiple reasons into
// a priority. (For ease of understanding the behavior.)
// - It's safe to renumber with preserving the order. Do it if needed.
//
// TODO(crbug.com/406403063): Rethink about granularity. We don't stick on the
// above policy. More rough priorities e.g. kBase/kHigh/kBurst might work well.
// This will eventually be done by `PrefetchPriority`(crbug.com/426404355).
//
// See also `PrefetchScheduler::NotifyAttributeMightChangedAndProgressAsync()`
// when you add a new one.
enum class PrefetchSchedulerPriority {
// Default.
kBase = 0,
// For tests. Do not use outside tests.
kHighTest = 1,
// Priority for prefetch ahead of prerender.
kHighAheadOfPrerender = 2,
// It's a threshold for burst. Do not use it in
// `PrefetchQueue::CalculatePriority()`. For burst, see a comment of
// `PrefetchScheduler`.
kBurstThreshold = 10,
// For tests. Do not use outside tests.
kBurstTest = 11,
// Priority derived from `PrefetchPriority`.
kBurstForPrefetchPriority = 12,
// Burst priority for prefetch ahead of prerender.
kBurstAheadOfPrerender = 13,
};
// Priority queue for prefetches
//
// It's a pure data structure and intended to be used in only
// `PrefetchScheduler`.
class PrefetchQueue {
public:
struct Item {
Item(base::WeakPtr<PrefetchContainer> prefetch_container,
PrefetchSchedulerPriority priority);
~Item();
// Movable but not copyable.
Item(const Item&&);
Item& operator=(const Item&&);
Item(const Item&) = delete;
Item& operator=(const Item&) = delete;
base::WeakPtr<PrefetchContainer> prefetch_container;
PrefetchSchedulerPriority priority;
};
PrefetchQueue();
~PrefetchQueue();
// Not movable nor copyable.
PrefetchQueue(const PrefetchQueue&&) = delete;
PrefetchQueue& operator=(const PrefetchQueue&&) = delete;
PrefetchQueue(const PrefetchQueue&) = delete;
PrefetchQueue& operator=(const PrefetchQueue&) = delete;
size_t size() const { return queue_.size(); }
void Push(base::WeakPtr<PrefetchContainer> prefetch_container,
PrefetchSchedulerPriority priority);
// Pops `PrefetchContainer`
//
// Returns the most high priority and first-in one iff found satisfying:
//
// - `pred`
// - Priority is larger or equal to `threshold_priority`.
template <class Predicate>
std::optional<PrefetchQueue::Item> Pop(
Predicate pred,
PrefetchSchedulerPriority threshold_priority) {
for (auto it = queue_.cbegin(); it != queue_.cend(); ++it) {
if (it->priority < threshold_priority) {
break;
}
if (pred(*it)) {
PrefetchQueue::Item ret = std::move(*it);
queue_.erase(it);
return std::move(ret);
}
}
return std::nullopt;
}
// Removes `PrefechContainer`.
//
// Returns true iff `PrefetchContainer` exists and is removed.
bool Remove(base::WeakPtr<PrefetchContainer> prefetch_container);
// Updates priority.
//
// Returns true iff priority is updated.
bool MaybeUpdatePriority(PrefetchContainer& prefetch_container,
PrefetchSchedulerPriority priority);
private:
std::vector<PrefetchQueue::Item> queue_;
};
// Schedules prefetches with limited concurrency.
//
// - Limits concurrently running prefetches.
// - Bursts and use a different limit if a `PrefetchContainer` has priority
// higher than `PrefetchPriority::kBurstThreshold`.
// - Manages waiting prefetches with a priority queue.
// - Prevents stuck (`queue_.size() > 0 && active_set_.size() == 0`).
//
// For more details, see
// https://docs.google.com/document/d/1W0Nk3Nq6NaUXkBppOUC5zyNmhVqMjYShm1bydGYd9qc
class CONTENT_EXPORT PrefetchScheduler {
public:
explicit PrefetchScheduler(PrefetchService* prefetch_service);
~PrefetchScheduler();
// Not movable nor copyable.
PrefetchScheduler(const PrefetchScheduler&&) = delete;
PrefetchScheduler& operator=(const PrefetchScheduler&&) = delete;
PrefetchScheduler(const PrefetchScheduler&) = delete;
PrefetchScheduler& operator=(const PrefetchScheduler&) = delete;
// See `active_set_`.
bool IsInActiveSet(const PrefetchContainer& prefetch_container);
// Modify-and-progress-async methods
//
// To prevent stuck, this class (and `PrefetchService`) must ensure that
// `Progress()` will be called eventually after modification. To ensure that,
// these modification methods automatically emit `Progress()` in async
// fashion.
//
// Contract:
//
// - `PrefetchService` must call `RemoveAndProgressAsync()` before weak
// pointer invalidation of the `PrefetchContainer`.
// - `PrefetchService` must call
// `NotifyAttributeMightChangedAndProgressAsync()` after modification of
// `PrefetchContainer` that can change priority.
//
// [#cant-detect-prefetch-document-manager] Note that `PrefetchScheduler`
// can't detect changes of `PrefetchDocumentManager` and
// `PrefetchDocumentManager::CanPrefetchNow()`. So, `PrefetchService` must
// explicitly call `Progress()`.
//
// If `should_progress` is false, doesn't call `ProgressAsync()`.
void PushAndProgress(PrefetchContainer& prefetch_container);
void PushAndProgressAsync(PrefetchContainer& prefetch_container);
// Note that this doesn't call `PrefetchService::ResetPrefetchContainer()`.
void RemoveAndProgressAsync(PrefetchContainer& prefetch_container,
bool should_progress = true);
void NotifyAttributeMightChangedAndProgressAsync(
PrefetchContainer& prefetch_container,
bool should_proggress = true);
// Progress scheduling
//
// This can call methods of `PrefetchService` with
// `PassKey<PrefetchScheduler>`, e.g. starting the load and eviction.
//
// Contract: See the comment [#cant-detect-prefetch-document-manager].
void Progress();
void SetCalculatePriorityForTesting(
base::RepeatingCallback<
PrefetchSchedulerPriority(const PrefetchContainer&)> callback);
private:
PrefetchSchedulerPriority CalculatePriority(
const PrefetchContainer& prefetch_container);
void ProgressAsync();
void ProgressOne(base::WeakPtr<PrefetchContainer> prefetch_container);
// Safety: This class is owned by `PrefetchService` and has the same lifetime.
const raw_ptr<PrefetchService> prefetch_service_;
// Max heap by priority
PrefetchQueue queue_;
// Active set of prefetches, i.e. prefetches that are in loading state
// (roughly).
//
// We use `std::vector` as set-like containers can't use `base::WeakPtr`.
std::vector<base::WeakPtr<PrefetchContainer>> active_set_;
// Used to reduce unnecessary multiple `Progress()` calls.
bool is_progress_scheduled_ = false;
// Used to prevent `PrefetchScheduler::ProgressOne()` ->
// `PrefetchService::EvictPrefetch()` ->
// `PrefetchScheduler::RemoveAndProgressAsync()` trigger
// `PrefetchScheduler::ProgressAsync()`.
bool in_eviction_ = false;
base::RepeatingCallback<PrefetchSchedulerPriority(const PrefetchContainer&)>
calculate_priority_for_test_;
#if DCHECK_IS_ON()
// Protects against `Progress()` being called recursively.
bool progress_reentrancy_guard_ = false;
#endif
base::WeakPtrFactory<PrefetchScheduler> weak_method_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SCHEDULER_H_