blob: 7cab89347c3eef26c8308aa6d429073a630f46cb [file] [log] [blame]
Bence Békybac04052022-04-07 15:44:29 -04001// Copyright (c) 2015 The Chromium Authors. All rights reserved.
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 QUICHE_HTTP2_CORE_PRIORITY_WRITE_SCHEDULER_H_
6#define QUICHE_HTTP2_CORE_PRIORITY_WRITE_SCHEDULER_H_
7
8#include <algorithm>
9#include <cstddef>
10#include <cstdint>
bnc82212742022-05-11 11:34:04 -070011#include <memory>
vasilvv243b2622023-11-07 17:01:30 -080012#include <optional>
Bence Békybac04052022-04-07 15:44:29 -040013#include <string>
14#include <tuple>
Bence Békybac04052022-04-07 15:44:29 -040015#include <utility>
16#include <vector>
17
bnc82212742022-05-11 11:34:04 -070018#include "absl/container/flat_hash_map.h"
Bence Békybac04052022-04-07 15:44:29 -040019#include "absl/strings/str_cat.h"
QUICHE team6ea70c82023-01-25 12:12:27 -080020#include "absl/time/time.h"
birenroycbb8cca2024-07-10 12:48:55 -070021#include "quiche/http2/core/spdy_protocol.h"
Bence Békybac04052022-04-07 15:44:29 -040022#include "quiche/common/platform/api/quiche_bug_tracker.h"
23#include "quiche/common/platform/api/quiche_export.h"
24#include "quiche/common/platform/api/quiche_logging.h"
25#include "quiche/common/quiche_circular_deque.h"
Bence Békybac04052022-04-07 15:44:29 -040026
27namespace http2 {
28
29namespace test {
30template <typename StreamIdType>
31class PriorityWriteSchedulerPeer;
32}
33
bnc447274a2023-01-18 14:22:21 -080034// SpdyPriority is an integer type, so this functor can be used both as
35// PriorityTypeToInt and as IntToPriorityType.
36struct QUICHE_EXPORT SpdyPriorityToSpdyPriority {
37 spdy::SpdyPriority operator()(spdy::SpdyPriority priority) {
38 return priority;
39 }
40};
41
bnc9044e052023-01-05 07:48:56 -080042// PriorityWriteScheduler manages the order in which HTTP/2 or HTTP/3 streams
bnc447274a2023-01-18 14:22:21 -080043// are written. Each stream has a priority of type PriorityType. This includes
44// an integer between 0 and 7, and optionally other information that is stored
45// but otherwise ignored by this class. Higher priority (lower integer value)
46// streams are always given precedence over lower priority (higher value)
47// streams, as long as the higher priority stream is not blocked.
Bence Békybac04052022-04-07 15:44:29 -040048//
bnc9044e052023-01-05 07:48:56 -080049// Each stream can be in one of two states: ready or not ready (for writing).
50// Ready state is changed by calling the MarkStreamReady() and
51// MarkStreamNotReady() methods. Only streams in the ready state can be returned
52// by PopNextReadyStream(). When returned by that method, the stream's state
53// changes to not ready.
Bence Békybac04052022-04-07 15:44:29 -040054//
bnc447274a2023-01-18 14:22:21 -080055template <typename StreamIdType, typename PriorityType = spdy::SpdyPriority,
56 typename PriorityTypeToInt = SpdyPriorityToSpdyPriority,
57 typename IntToPriorityType = SpdyPriorityToSpdyPriority>
bnc9044e052023-01-05 07:48:56 -080058class QUICHE_EXPORT PriorityWriteScheduler {
Bence Békybac04052022-04-07 15:44:29 -040059 public:
bnc727431e2023-01-05 10:12:24 -080060 static constexpr int kHighestPriority = 0;
61 static constexpr int kLowestPriority = 7;
62
63 static_assert(spdy::kV3HighestPriority == kHighestPriority);
64 static_assert(spdy::kV3LowestPriority == kLowestPriority);
65
bnc9044e052023-01-05 07:48:56 -080066 // Registers new stream `stream_id` with the scheduler, assigning it the
bncad5c27b2023-01-18 14:18:56 -080067 // given priority.
bnc9044e052023-01-05 07:48:56 -080068 //
bncad5c27b2023-01-18 14:18:56 -080069 // Preconditions: `stream_id` should be unregistered.
70 void RegisterStream(StreamIdType stream_id, PriorityType priority) {
bnc447274a2023-01-18 14:22:21 -080071 auto stream_info = std::make_unique<StreamInfo>(
72 StreamInfo{std::move(priority), stream_id, false});
Bence Békybac04052022-04-07 15:44:29 -040073 bool inserted =
bnc82212742022-05-11 11:34:04 -070074 stream_infos_.insert(std::make_pair(stream_id, std::move(stream_info)))
75 .second;
Bence Békybac04052022-04-07 15:44:29 -040076 QUICHE_BUG_IF(spdy_bug_19_2, !inserted)
77 << "Stream " << stream_id << " already registered";
78 }
79
bnc9044e052023-01-05 07:48:56 -080080 // Unregisters the given stream from the scheduler, which will no longer keep
81 // state for it.
82 //
83 // Preconditions: `stream_id` should be registered.
84 void UnregisterStream(StreamIdType stream_id) {
Bence Békybac04052022-04-07 15:44:29 -040085 auto it = stream_infos_.find(stream_id);
86 if (it == stream_infos_.end()) {
87 QUICHE_BUG(spdy_bug_19_3) << "Stream " << stream_id << " not registered";
88 return;
89 }
bnc82212742022-05-11 11:34:04 -070090 const StreamInfo* const stream_info = it->second.get();
91 if (stream_info->ready) {
bnc447274a2023-01-18 14:22:21 -080092 bool erased =
93 Erase(&priority_infos_[PriorityTypeToInt()(stream_info->priority)]
94 .ready_list,
95 stream_info);
Bence Békybac04052022-04-07 15:44:29 -040096 QUICHE_DCHECK(erased);
97 }
98 stream_infos_.erase(it);
99 }
100
bnc9044e052023-01-05 07:48:56 -0800101 // Returns true if the given stream is currently registered.
102 bool StreamRegistered(StreamIdType stream_id) const {
Bence Békybac04052022-04-07 15:44:29 -0400103 return stream_infos_.find(stream_id) != stream_infos_.end();
104 }
105
bncad5c27b2023-01-18 14:18:56 -0800106 // Returns the priority of the specified stream.
bnc9044e052023-01-05 07:48:56 -0800107 //
108 // Preconditions: `stream_id` should be registered.
bncad5c27b2023-01-18 14:18:56 -0800109 PriorityType GetStreamPriority(StreamIdType stream_id) const {
Bence Békybac04052022-04-07 15:44:29 -0400110 auto it = stream_infos_.find(stream_id);
111 if (it == stream_infos_.end()) {
112 QUICHE_DVLOG(1) << "Stream " << stream_id << " not registered";
bnc447274a2023-01-18 14:22:21 -0800113 return IntToPriorityType()(kLowestPriority);
Bence Békybac04052022-04-07 15:44:29 -0400114 }
bncad5c27b2023-01-18 14:18:56 -0800115 return it->second->priority;
Bence Békybac04052022-04-07 15:44:29 -0400116 }
117
bncad5c27b2023-01-18 14:18:56 -0800118 // Updates the priority of the given stream.
bnc9044e052023-01-05 07:48:56 -0800119 //
bncad5c27b2023-01-18 14:18:56 -0800120 // Preconditions: `stream_id` should be registered.
121 void UpdateStreamPriority(StreamIdType stream_id, PriorityType priority) {
Bence Békybac04052022-04-07 15:44:29 -0400122 auto it = stream_infos_.find(stream_id);
123 if (it == stream_infos_.end()) {
124 // TODO(mpw): add to stream_infos_ on demand--see b/15676312.
125 QUICHE_DVLOG(1) << "Stream " << stream_id << " not registered";
126 return;
127 }
bnc447274a2023-01-18 14:22:21 -0800128
bnc82212742022-05-11 11:34:04 -0700129 StreamInfo* const stream_info = it->second.get();
bncad5c27b2023-01-18 14:18:56 -0800130 if (stream_info->priority == priority) {
Bence Békybac04052022-04-07 15:44:29 -0400131 return;
132 }
bnc447274a2023-01-18 14:22:21 -0800133
134 // Only move `stream_info` to a different bucket if the integral priority
135 // value changes.
136 if (PriorityTypeToInt()(stream_info->priority) !=
137 PriorityTypeToInt()(priority) &&
138 stream_info->ready) {
139 bool erased =
140 Erase(&priority_infos_[PriorityTypeToInt()(stream_info->priority)]
141 .ready_list,
142 stream_info);
Bence Békybac04052022-04-07 15:44:29 -0400143 QUICHE_DCHECK(erased);
bnc447274a2023-01-18 14:22:21 -0800144 priority_infos_[PriorityTypeToInt()(priority)].ready_list.push_back(
145 stream_info);
Bence Békybac04052022-04-07 15:44:29 -0400146 ++num_ready_streams_;
147 }
bnc447274a2023-01-18 14:22:21 -0800148
149 // But override `priority` for the stream regardless of the integral value,
150 // because it might contain additional information.
151 stream_info->priority = std::move(priority);
Bence Békybac04052022-04-07 15:44:29 -0400152 }
153
QUICHE team6ea70c82023-01-25 12:12:27 -0800154 // Records time of a read/write event for the given stream.
bnc9044e052023-01-05 07:48:56 -0800155 //
156 // Preconditions: `stream_id` should be registered.
QUICHE team6ea70c82023-01-25 12:12:27 -0800157 void RecordStreamEventTime(StreamIdType stream_id, absl::Time now) {
Bence Békybac04052022-04-07 15:44:29 -0400158 auto it = stream_infos_.find(stream_id);
159 if (it == stream_infos_.end()) {
160 QUICHE_BUG(spdy_bug_19_4) << "Stream " << stream_id << " not registered";
161 return;
162 }
bnc447274a2023-01-18 14:22:21 -0800163 PriorityInfo& priority_info =
164 priority_infos_[PriorityTypeToInt()(it->second->priority)];
QUICHE team6ea70c82023-01-25 12:12:27 -0800165 priority_info.last_event_time =
166 std::max(priority_info.last_event_time, absl::make_optional(now));
Bence Békybac04052022-04-07 15:44:29 -0400167 }
168
QUICHE team6ea70c82023-01-25 12:12:27 -0800169 // Returns time of the last read/write event for a stream with higher priority
170 // than the priority of the given stream, or nullopt if there is no such
171 // event.
bnc9044e052023-01-05 07:48:56 -0800172 //
173 // Preconditions: `stream_id` should be registered.
vasilvv243b2622023-11-07 17:01:30 -0800174 std::optional<absl::Time> GetLatestEventWithPriority(
QUICHE team6ea70c82023-01-25 12:12:27 -0800175 StreamIdType stream_id) const {
Bence Békybac04052022-04-07 15:44:29 -0400176 auto it = stream_infos_.find(stream_id);
177 if (it == stream_infos_.end()) {
178 QUICHE_BUG(spdy_bug_19_5) << "Stream " << stream_id << " not registered";
vasilvv243b2622023-11-07 17:01:30 -0800179 return std::nullopt;
Bence Békybac04052022-04-07 15:44:29 -0400180 }
vasilvv243b2622023-11-07 17:01:30 -0800181 std::optional<absl::Time> last_event_time;
bnc82212742022-05-11 11:34:04 -0700182 const StreamInfo* const stream_info = it->second.get();
bnc447274a2023-01-18 14:22:21 -0800183 for (int p = kHighestPriority;
184 p < PriorityTypeToInt()(stream_info->priority); ++p) {
QUICHE team6ea70c82023-01-25 12:12:27 -0800185 last_event_time =
186 std::max(last_event_time, priority_infos_[p].last_event_time);
Bence Békybac04052022-04-07 15:44:29 -0400187 }
QUICHE team6ea70c82023-01-25 12:12:27 -0800188 return last_event_time;
Bence Békybac04052022-04-07 15:44:29 -0400189 }
190
bnc9044e052023-01-05 07:48:56 -0800191 // If the scheduler has any ready streams, returns the next scheduled
192 // ready stream, in the process transitioning the stream from ready to not
193 // ready.
194 //
195 // Preconditions: `HasReadyStreams() == true`
196 StreamIdType PopNextReadyStream() {
bncad5c27b2023-01-18 14:18:56 -0800197 return std::get<0>(PopNextReadyStreamAndPriority());
Bence Békybac04052022-04-07 15:44:29 -0400198 }
199
bnc9044e052023-01-05 07:48:56 -0800200 // If the scheduler has any ready streams, returns the next scheduled
201 // ready stream and its priority, in the process transitioning the stream from
202 // ready to not ready.
203 //
204 // Preconditions: `HasReadyStreams() == true`
bncad5c27b2023-01-18 14:18:56 -0800205 std::tuple<StreamIdType, PriorityType> PopNextReadyStreamAndPriority() {
bnc447274a2023-01-18 14:22:21 -0800206 for (int p = kHighestPriority; p <= kLowestPriority; ++p) {
Bence Békybac04052022-04-07 15:44:29 -0400207 ReadyList& ready_list = priority_infos_[p].ready_list;
208 if (!ready_list.empty()) {
bnc82212742022-05-11 11:34:04 -0700209 StreamInfo* const info = ready_list.front();
Bence Békybac04052022-04-07 15:44:29 -0400210 ready_list.pop_front();
211 --num_ready_streams_;
212
213 QUICHE_DCHECK(stream_infos_.find(info->stream_id) !=
214 stream_infos_.end());
215 info->ready = false;
bncad5c27b2023-01-18 14:18:56 -0800216 return std::make_tuple(info->stream_id, info->priority);
Bence Békybac04052022-04-07 15:44:29 -0400217 }
218 }
219 QUICHE_BUG(spdy_bug_19_6) << "No ready streams available";
bnc447274a2023-01-18 14:22:21 -0800220 return std::make_tuple(0, IntToPriorityType()(kLowestPriority));
Bence Békybac04052022-04-07 15:44:29 -0400221 }
222
bnc9044e052023-01-05 07:48:56 -0800223 // Returns true if there's another stream ahead of the given stream in the
224 // scheduling queue. This function can be called to see if the given stream
225 // should yield work to another stream.
226 //
227 // Preconditions: `stream_id` should be registered.
228 bool ShouldYield(StreamIdType stream_id) const {
Bence Békybac04052022-04-07 15:44:29 -0400229 auto it = stream_infos_.find(stream_id);
230 if (it == stream_infos_.end()) {
231 QUICHE_BUG(spdy_bug_19_7) << "Stream " << stream_id << " not registered";
232 return false;
233 }
234
235 // If there's a higher priority stream, this stream should yield.
bnc82212742022-05-11 11:34:04 -0700236 const StreamInfo* const stream_info = it->second.get();
bnc447274a2023-01-18 14:22:21 -0800237 for (int p = kHighestPriority;
238 p < PriorityTypeToInt()(stream_info->priority); ++p) {
Bence Békybac04052022-04-07 15:44:29 -0400239 if (!priority_infos_[p].ready_list.empty()) {
240 return true;
241 }
242 }
243
244 // If this priority level is empty, or this stream is the next up, there's
245 // no need to yield.
bnc447274a2023-01-18 14:22:21 -0800246 const auto& ready_list =
247 priority_infos_[PriorityTypeToInt()(it->second->priority)].ready_list;
Bence Békybac04052022-04-07 15:44:29 -0400248 if (ready_list.empty() || ready_list.front()->stream_id == stream_id) {
249 return false;
250 }
251
252 // There are other streams in this priority level which take precedence.
253 // Yield.
254 return true;
255 }
256
bnc9044e052023-01-05 07:48:56 -0800257 // Marks the stream as ready to write. If the stream was already ready, does
258 // nothing. If add_to_front is true, the stream is scheduled ahead of other
259 // streams of the same priority/weight, otherwise it is scheduled behind them.
260 //
261 // Preconditions: `stream_id` should be registered.
262 void MarkStreamReady(StreamIdType stream_id, bool add_to_front) {
Bence Békybac04052022-04-07 15:44:29 -0400263 auto it = stream_infos_.find(stream_id);
264 if (it == stream_infos_.end()) {
265 QUICHE_BUG(spdy_bug_19_8) << "Stream " << stream_id << " not registered";
266 return;
267 }
bnc82212742022-05-11 11:34:04 -0700268 StreamInfo* const stream_info = it->second.get();
269 if (stream_info->ready) {
Bence Békybac04052022-04-07 15:44:29 -0400270 return;
271 }
bnc447274a2023-01-18 14:22:21 -0800272 ReadyList& ready_list =
273 priority_infos_[PriorityTypeToInt()(stream_info->priority)].ready_list;
Bence Békybac04052022-04-07 15:44:29 -0400274 if (add_to_front) {
bnc82212742022-05-11 11:34:04 -0700275 ready_list.push_front(stream_info);
Bence Békybac04052022-04-07 15:44:29 -0400276 } else {
bnc82212742022-05-11 11:34:04 -0700277 ready_list.push_back(stream_info);
Bence Békybac04052022-04-07 15:44:29 -0400278 }
279 ++num_ready_streams_;
bnc82212742022-05-11 11:34:04 -0700280 stream_info->ready = true;
Bence Békybac04052022-04-07 15:44:29 -0400281 }
282
bnc9044e052023-01-05 07:48:56 -0800283 // Marks the stream as not ready to write. If the stream is not registered or
284 // not ready, does nothing.
285 //
286 // Preconditions: `stream_id` should be registered.
287 void MarkStreamNotReady(StreamIdType stream_id) {
Bence Békybac04052022-04-07 15:44:29 -0400288 auto it = stream_infos_.find(stream_id);
289 if (it == stream_infos_.end()) {
290 QUICHE_BUG(spdy_bug_19_9) << "Stream " << stream_id << " not registered";
291 return;
292 }
bnc82212742022-05-11 11:34:04 -0700293 StreamInfo* const stream_info = it->second.get();
294 if (!stream_info->ready) {
Bence Békybac04052022-04-07 15:44:29 -0400295 return;
296 }
bnc447274a2023-01-18 14:22:21 -0800297 bool erased = Erase(
298 &priority_infos_[PriorityTypeToInt()(stream_info->priority)].ready_list,
299 stream_info);
Bence Békybac04052022-04-07 15:44:29 -0400300 QUICHE_DCHECK(erased);
bnc82212742022-05-11 11:34:04 -0700301 stream_info->ready = false;
Bence Békybac04052022-04-07 15:44:29 -0400302 }
303
bnc9044e052023-01-05 07:48:56 -0800304 // Returns true iff the scheduler has any ready streams.
305 bool HasReadyStreams() const { return num_ready_streams_ > 0; }
Bence Békybac04052022-04-07 15:44:29 -0400306
bnc9044e052023-01-05 07:48:56 -0800307 // Returns the number of streams currently marked ready.
308 size_t NumReadyStreams() const { return num_ready_streams_; }
Bence Békybac04052022-04-07 15:44:29 -0400309
bnc9044e052023-01-05 07:48:56 -0800310 // Returns the number of registered streams.
311 size_t NumRegisteredStreams() const { return stream_infos_.size(); }
Bence Békybac04052022-04-07 15:44:29 -0400312
bnc9044e052023-01-05 07:48:56 -0800313 // Returns summary of internal state, for logging/debugging.
314 std::string DebugString() const {
Bence Békybac04052022-04-07 15:44:29 -0400315 return absl::StrCat(
316 "PriorityWriteScheduler {num_streams=", stream_infos_.size(),
317 " num_ready_streams=", NumReadyStreams(), "}");
318 }
319
bnc9044e052023-01-05 07:48:56 -0800320 // Returns true if stream with `stream_id` is ready.
321 bool IsStreamReady(StreamIdType stream_id) const {
Bence Békybac04052022-04-07 15:44:29 -0400322 auto it = stream_infos_.find(stream_id);
323 if (it == stream_infos_.end()) {
324 QUICHE_DLOG(INFO) << "Stream " << stream_id << " not registered";
325 return false;
326 }
bnc82212742022-05-11 11:34:04 -0700327 return it->second->ready;
Bence Békybac04052022-04-07 15:44:29 -0400328 }
329
330 private:
331 friend class test::PriorityWriteSchedulerPeer<StreamIdType>;
332
bncad5c27b2023-01-18 14:18:56 -0800333 // State kept for all registered streams.
334 // All ready streams have `ready == true` and should be present in
335 // `priority_infos_[priority].ready_list`.
vasilvve124b7b2022-10-21 10:09:44 -0700336 struct QUICHE_EXPORT StreamInfo {
bncad5c27b2023-01-18 14:18:56 -0800337 PriorityType priority;
Bence Békybac04052022-04-07 15:44:29 -0400338 StreamIdType stream_id;
339 bool ready;
340 };
341
342 // O(1) size lookup, O(1) insert at front or back (amortized).
343 using ReadyList = quiche::QuicheCircularDeque<StreamInfo*>;
344
345 // State kept for each priority level.
vasilvve124b7b2022-10-21 10:09:44 -0700346 struct QUICHE_EXPORT PriorityInfo {
Bence Békybac04052022-04-07 15:44:29 -0400347 // IDs of streams that are ready to write.
348 ReadyList ready_list;
QUICHE team6ea70c82023-01-25 12:12:27 -0800349 // Time of latest write event for stream of this priority.
vasilvv243b2622023-11-07 17:01:30 -0800350 std::optional<absl::Time> last_event_time;
Bence Békybac04052022-04-07 15:44:29 -0400351 };
352
bnc82212742022-05-11 11:34:04 -0700353 // Use std::unique_ptr, because absl::flat_hash_map does not have pointer
354 // stability, but ReadyList stores pointers to the StreamInfo objects.
355 using StreamInfoMap =
356 absl::flat_hash_map<StreamIdType, std::unique_ptr<StreamInfo>>;
Bence Békybac04052022-04-07 15:44:29 -0400357
358 // Erases `info` from `ready_list`, returning true if found (and erased), or
359 // false otherwise. Decrements `num_ready_streams_` if an entry is erased.
bnc82212742022-05-11 11:34:04 -0700360 bool Erase(ReadyList* ready_list, const StreamInfo* info) {
361 auto it = std::remove(ready_list->begin(), ready_list->end(), info);
Bence Békybac04052022-04-07 15:44:29 -0400362 if (it == ready_list->end()) {
363 // `info` was not found.
364 return false;
365 }
366 ready_list->pop_back();
367 --num_ready_streams_;
368 return true;
369 }
370
371 // Number of ready streams.
372 size_t num_ready_streams_ = 0;
373 // Per-priority state, including ready lists.
bnc727431e2023-01-05 10:12:24 -0800374 PriorityInfo priority_infos_[kLowestPriority + 1];
Bence Békybac04052022-04-07 15:44:29 -0400375 // StreamInfos for all registered streams.
376 StreamInfoMap stream_infos_;
Bence Békybac04052022-04-07 15:44:29 -0400377};
378
379} // namespace http2
380
381#endif // QUICHE_HTTP2_CORE_PRIORITY_WRITE_SCHEDULER_H_