kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 1 | // 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 | #ifndef CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SCHEDULER_H_ |
| 6 | #define CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SCHEDULER_H_ |
| 7 | |
| 8 | #include "base/functional/callback.h" |
| 9 | #include "base/memory/weak_ptr.h" |
| 10 | #include "content/common/content_export.h" |
| 11 | |
| 12 | namespace content { |
| 13 | |
| 14 | class PrefetchContainer; |
| 15 | class PrefetchService; |
| 16 | |
| 17 | // Priority of `PrefetchContainer` |
| 18 | // |
| 19 | // - Larger values take priority. |
| 20 | // - If `PrefetchContainer` has a priority larger than `kBurstThreshold`, it |
| 21 | // will be executed with additional capacity. For more details, see the |
| 22 | // comment #algorithm in `PrefetchScheduler::Progress()`. |
| 23 | // - Note that priority should be explicitly re-calculated if related attributes |
| 24 | // of `PrefetchContainer` are changed. See |
| 25 | // `PrefetchScheduler::CalculatePriority()`, the call sites, and |
| 26 | // `PrefetchScheduler::NotifyAttributeMightChangedAndProgressAsync()`. |
| 27 | // |
| 28 | // Maintenance policy: |
| 29 | // |
| 30 | // - Put a priority per reason. Think twice before mixing multiple reasons into |
| 31 | // a priority. (For ease of understanding the behavior.) |
| 32 | // - It's safe to renumber with preserving the order. Do it if needed. |
| 33 | // |
| 34 | // TODO(crbug.com/406403063): Rethink about granularity. We don't stick on the |
| 35 | // above policy. More rough priorities e.g. kBase/kHigh/kBurst might work well. |
Taiyo Mizuhashi | 53da92d | 2025-06-23 08:56:15 | [diff] [blame] | 36 | // This will eventually be done by `PrefetchPriority`(crbug.com/426404355). |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 37 | // |
| 38 | // See also `PrefetchScheduler::NotifyAttributeMightChangedAndProgressAsync()` |
| 39 | // when you add a new one. |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 40 | enum class PrefetchSchedulerPriority { |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 41 | // Default. |
| 42 | kBase = 0, |
| 43 | // For tests. Do not use outside tests. |
| 44 | kHighTest = 1, |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 45 | // Priority for prefetch ahead of prerender. |
| 46 | kHighAheadOfPrerender = 2, |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 47 | // It's a threshold for burst. Do not use it in |
| 48 | // `PrefetchQueue::CalculatePriority()`. For burst, see a comment of |
| 49 | // `PrefetchScheduler`. |
| 50 | kBurstThreshold = 10, |
| 51 | // For tests. Do not use outside tests. |
| 52 | kBurstTest = 11, |
Taiyo Mizuhashi | 53da92d | 2025-06-23 08:56:15 | [diff] [blame] | 53 | // Priority derived from `PrefetchPriority`. |
| 54 | kBurstForPrefetchPriority = 12, |
kenoss | 1eb925f | 2025-04-04 05:49:56 | [diff] [blame] | 55 | // Burst priority for prefetch ahead of prerender. |
Taiyo Mizuhashi | 53da92d | 2025-06-23 08:56:15 | [diff] [blame] | 56 | kBurstAheadOfPrerender = 13, |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 57 | }; |
| 58 | |
| 59 | // Priority queue for prefetches |
| 60 | // |
| 61 | // It's a pure data structure and intended to be used in only |
| 62 | // `PrefetchScheduler`. |
| 63 | class PrefetchQueue { |
| 64 | public: |
| 65 | struct Item { |
| 66 | Item(base::WeakPtr<PrefetchContainer> prefetch_container, |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 67 | PrefetchSchedulerPriority priority); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 68 | ~Item(); |
| 69 | |
| 70 | // Movable but not copyable. |
| 71 | Item(const Item&&); |
| 72 | Item& operator=(const Item&&); |
| 73 | Item(const Item&) = delete; |
| 74 | Item& operator=(const Item&) = delete; |
| 75 | |
| 76 | base::WeakPtr<PrefetchContainer> prefetch_container; |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 77 | PrefetchSchedulerPriority priority; |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 78 | }; |
| 79 | |
| 80 | PrefetchQueue(); |
| 81 | ~PrefetchQueue(); |
| 82 | |
| 83 | // Not movable nor copyable. |
| 84 | PrefetchQueue(const PrefetchQueue&&) = delete; |
| 85 | PrefetchQueue& operator=(const PrefetchQueue&&) = delete; |
| 86 | PrefetchQueue(const PrefetchQueue&) = delete; |
| 87 | PrefetchQueue& operator=(const PrefetchQueue&) = delete; |
| 88 | |
| 89 | size_t size() const { return queue_.size(); } |
| 90 | |
| 91 | void Push(base::WeakPtr<PrefetchContainer> prefetch_container, |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 92 | PrefetchSchedulerPriority priority); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 93 | |
| 94 | // Pops `PrefetchContainer` |
| 95 | // |
| 96 | // Returns the most high priority and first-in one iff found satisfying: |
| 97 | // |
| 98 | // - `pred` |
| 99 | // - Priority is larger or equal to `threshold_priority`. |
| 100 | template <class Predicate> |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 101 | std::optional<PrefetchQueue::Item> Pop( |
| 102 | Predicate pred, |
| 103 | PrefetchSchedulerPriority threshold_priority) { |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 104 | for (auto it = queue_.cbegin(); it != queue_.cend(); ++it) { |
| 105 | if (it->priority < threshold_priority) { |
| 106 | break; |
| 107 | } |
| 108 | |
| 109 | if (pred(*it)) { |
| 110 | PrefetchQueue::Item ret = std::move(*it); |
| 111 | queue_.erase(it); |
| 112 | return std::move(ret); |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | return std::nullopt; |
| 117 | } |
| 118 | |
| 119 | // Removes `PrefechContainer`. |
| 120 | // |
| 121 | // Returns true iff `PrefetchContainer` exists and is removed. |
| 122 | bool Remove(base::WeakPtr<PrefetchContainer> prefetch_container); |
| 123 | // Updates priority. |
| 124 | // |
| 125 | // Returns true iff priority is updated. |
| 126 | bool MaybeUpdatePriority(PrefetchContainer& prefetch_container, |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 127 | PrefetchSchedulerPriority priority); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 128 | |
| 129 | private: |
| 130 | std::vector<PrefetchQueue::Item> queue_; |
| 131 | }; |
| 132 | |
| 133 | // Schedules prefetches with limited concurrency. |
| 134 | // |
| 135 | // - Limits concurrently running prefetches. |
| 136 | // - Bursts and use a different limit if a `PrefetchContainer` has priority |
| 137 | // higher than `PrefetchPriority::kBurstThreshold`. |
| 138 | // - Manages waiting prefetches with a priority queue. |
| 139 | // - Prevents stuck (`queue_.size() > 0 && active_set_.size() == 0`). |
| 140 | // |
| 141 | // For more details, see |
| 142 | // https://docs.google.com/document/d/1W0Nk3Nq6NaUXkBppOUC5zyNmhVqMjYShm1bydGYd9qc |
| 143 | class CONTENT_EXPORT PrefetchScheduler { |
| 144 | public: |
| 145 | explicit PrefetchScheduler(PrefetchService* prefetch_service); |
| 146 | ~PrefetchScheduler(); |
| 147 | |
| 148 | // Not movable nor copyable. |
| 149 | PrefetchScheduler(const PrefetchScheduler&&) = delete; |
| 150 | PrefetchScheduler& operator=(const PrefetchScheduler&&) = delete; |
| 151 | PrefetchScheduler(const PrefetchScheduler&) = delete; |
| 152 | PrefetchScheduler& operator=(const PrefetchScheduler&) = delete; |
| 153 | |
| 154 | // See `active_set_`. |
| 155 | bool IsInActiveSet(const PrefetchContainer& prefetch_container); |
| 156 | |
| 157 | // Modify-and-progress-async methods |
| 158 | // |
| 159 | // To prevent stuck, this class (and `PrefetchService`) must ensure that |
| 160 | // `Progress()` will be called eventually after modification. To ensure that, |
| 161 | // these modification methods automatically emit `Progress()` in async |
| 162 | // fashion. |
| 163 | // |
| 164 | // Contract: |
| 165 | // |
| 166 | // - `PrefetchService` must call `RemoveAndProgressAsync()` before weak |
| 167 | // pointer invalidation of the `PrefetchContainer`. |
| 168 | // - `PrefetchService` must call |
| 169 | // `NotifyAttributeMightChangedAndProgressAsync()` after modification of |
| 170 | // `PrefetchContainer` that can change priority. |
| 171 | // |
| 172 | // [#cant-detect-prefetch-document-manager] Note that `PrefetchScheduler` |
| 173 | // can't detect changes of `PrefetchDocumentManager` and |
| 174 | // `PrefetchDocumentManager::CanPrefetchNow()`. So, `PrefetchService` must |
| 175 | // explicitly call `Progress()`. |
kenoss | e8d8bbb5 | 2025-05-13 12:35:04 | [diff] [blame] | 176 | // |
| 177 | // If `should_progress` is false, doesn't call `ProgressAsync()`. |
kenoss | 0a56736 | 2025-05-16 11:18:55 | [diff] [blame] | 178 | void PushAndProgress(PrefetchContainer& prefetch_container); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 179 | void PushAndProgressAsync(PrefetchContainer& prefetch_container); |
| 180 | // Note that this doesn't call `PrefetchService::ResetPrefetchContainer()`. |
kenoss | e8d8bbb5 | 2025-05-13 12:35:04 | [diff] [blame] | 181 | void RemoveAndProgressAsync(PrefetchContainer& prefetch_container, |
| 182 | bool should_progress = true); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 183 | void NotifyAttributeMightChangedAndProgressAsync( |
kenoss | e8d8bbb5 | 2025-05-13 12:35:04 | [diff] [blame] | 184 | PrefetchContainer& prefetch_container, |
| 185 | bool should_proggress = true); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 186 | |
| 187 | // Progress scheduling |
| 188 | // |
| 189 | // This can call methods of `PrefetchService` with |
| 190 | // `PassKey<PrefetchScheduler>`, e.g. starting the load and eviction. |
| 191 | // |
| 192 | // Contract: See the comment [#cant-detect-prefetch-document-manager]. |
| 193 | void Progress(); |
| 194 | |
| 195 | void SetCalculatePriorityForTesting( |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 196 | base::RepeatingCallback< |
| 197 | PrefetchSchedulerPriority(const PrefetchContainer&)> callback); |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 198 | |
| 199 | private: |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 200 | PrefetchSchedulerPriority CalculatePriority( |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 201 | const PrefetchContainer& prefetch_container); |
| 202 | |
| 203 | void ProgressAsync(); |
| 204 | void ProgressOne(base::WeakPtr<PrefetchContainer> prefetch_container); |
| 205 | |
| 206 | // Safety: This class is owned by `PrefetchService` and has the same lifetime. |
| 207 | const raw_ptr<PrefetchService> prefetch_service_; |
| 208 | |
| 209 | // Max heap by priority |
| 210 | PrefetchQueue queue_; |
| 211 | // Active set of prefetches, i.e. prefetches that are in loading state |
| 212 | // (roughly). |
| 213 | // |
| 214 | // We use `std::vector` as set-like containers can't use `base::WeakPtr`. |
| 215 | std::vector<base::WeakPtr<PrefetchContainer>> active_set_; |
| 216 | |
| 217 | // Used to reduce unnecessary multiple `Progress()` calls. |
| 218 | bool is_progress_scheduled_ = false; |
| 219 | // Used to prevent `PrefetchScheduler::ProgressOne()` -> |
| 220 | // `PrefetchService::EvictPrefetch()` -> |
| 221 | // `PrefetchScheduler::RemoveAndProgressAsync()` trigger |
| 222 | // `PrefetchScheduler::ProgressAsync()`. |
| 223 | bool in_eviction_ = false; |
| 224 | |
Taiyo Mizuhashi | d7c85699 | 2025-06-23 08:56:02 | [diff] [blame] | 225 | base::RepeatingCallback<PrefetchSchedulerPriority(const PrefetchContainer&)> |
kenoss | 1b8ebc2 | 2025-04-04 00:11:35 | [diff] [blame] | 226 | calculate_priority_for_test_; |
| 227 | |
| 228 | #if DCHECK_IS_ON() |
| 229 | // Protects against `Progress()` being called recursively. |
| 230 | bool progress_reentrancy_guard_ = false; |
| 231 | #endif |
| 232 | |
| 233 | base::WeakPtrFactory<PrefetchScheduler> weak_method_factory_{this}; |
| 234 | }; |
| 235 | |
| 236 | } // namespace content |
| 237 | |
| 238 | #endif // CONTENT_BROWSER_PRELOADING_PREFETCH_PREFETCH_SCHEDULER_H_ |