blob: b7046e7023a8a89b345f218b2ec34c9c322845ed [file] [log] [blame]
fayang89b9af52019-07-23 07:52:49 -07001// Copyright (c) 2019 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
QUICHE team18371262021-04-08 09:00:15 -07005#ifndef QUICHE_HTTP2_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_
6#define QUICHE_HTTP2_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_
fayang89b9af52019-07-23 07:52:49 -07007
8#include <cstdint>
9#include <deque>
10#include <map>
11#include <memory>
12#include <queue>
13#include <set>
bnc44712912019-08-15 18:58:14 -070014#include <string>
fayang89b9af52019-07-23 07:52:49 -070015#include <tuple>
16#include <unordered_map>
17#include <utility>
18#include <vector>
19
bnc93e89182021-04-29 10:03:53 -040020#include "absl/container/flat_hash_map.h"
vasilvv1ea0b542020-12-03 15:21:00 -080021#include "absl/strings/str_cat.h"
QUICHE team18371262021-04-08 09:00:15 -070022#include "http2/core/write_scheduler.h"
QUICHE team33a4aa62021-04-08 11:10:59 -070023#include "common/platform/api/quiche_bug_tracker.h"
QUICHE team1b5d09e2021-04-08 13:36:42 -070024#include "common/platform/api/quiche_logging.h"
QUICHE team5be974e2020-12-29 18:35:24 -050025#include "spdy/core/spdy_intrusive_list.h"
26#include "spdy/core/spdy_protocol.h"
QUICHE team5be974e2020-12-29 18:35:24 -050027#include "spdy/platform/api/spdy_string_utils.h"
fayang89b9af52019-07-23 07:52:49 -070028
QUICHE team18371262021-04-08 09:00:15 -070029namespace http2 {
fayang89b9af52019-07-23 07:52:49 -070030
31namespace test {
32template <typename StreamIdType>
33class Http2PriorityWriteSchedulerPeer;
34}
35
36// This data structure implements the HTTP/2 stream priority tree defined in
37// section 5.3 of RFC 7540:
38// http://tools.ietf.org/html/rfc7540#section-5.3
39//
40// Streams can be added and removed, and dependencies between them defined.
41// Streams constitute a tree rooted at stream ID 0: each stream has a single
42// parent stream, and 0 or more child streams. Individual streams can be
43// marked as ready to read/write, and then the whole structure can be queried
44// to pick the next stream to read/write out of those that are ready.
45template <typename StreamIdType>
46class Http2PriorityWriteScheduler : public WriteScheduler<StreamIdType> {
47 public:
48 using typename WriteScheduler<StreamIdType>::StreamPrecedenceType;
49
50 Http2PriorityWriteScheduler();
51 Http2PriorityWriteScheduler(const Http2PriorityWriteScheduler&) = delete;
52 Http2PriorityWriteScheduler& operator=(const Http2PriorityWriteScheduler&) =
53 delete;
54
55 // WriteScheduler methods
56 void RegisterStream(StreamIdType stream_id,
57 const StreamPrecedenceType& precedence) override;
58 void UnregisterStream(StreamIdType stream_id) override;
59 bool StreamRegistered(StreamIdType stream_id) const override;
60 StreamPrecedenceType GetStreamPrecedence(
61 StreamIdType stream_id) const override;
62 void UpdateStreamPrecedence(StreamIdType stream_id,
63 const StreamPrecedenceType& precedence) override;
64 std::vector<StreamIdType> GetStreamChildren(
65 StreamIdType stream_id) const override;
66 void RecordStreamEventTime(StreamIdType stream_id,
67 int64_t now_in_usec) override;
68 int64_t GetLatestEventWithPrecedence(StreamIdType stream_id) const override;
69 bool ShouldYield(StreamIdType stream_id) const override;
70 void MarkStreamReady(StreamIdType stream_id, bool add_to_front) override;
71 void MarkStreamNotReady(StreamIdType stream_id) override;
72 bool HasReadyStreams() const override;
73 StreamIdType PopNextReadyStream() override;
74 std::tuple<StreamIdType, StreamPrecedenceType>
75 PopNextReadyStreamAndPrecedence() override;
76 size_t NumReadyStreams() const override;
77 bool IsStreamReady(StreamIdType stream_id) const override;
fayang753002d2019-07-24 11:54:43 -070078 size_t NumRegisteredStreams() const override;
bnc44712912019-08-15 18:58:14 -070079 std::string DebugString() const override;
fayang89b9af52019-07-23 07:52:49 -070080
fayang89b9af52019-07-23 07:52:49 -070081 private:
82 friend class test::Http2PriorityWriteSchedulerPeer<StreamIdType>;
83
84 struct StreamInfo;
bnc13067cf2021-04-01 12:07:01 -070085 using StreamInfoVector = absl::InlinedVector<StreamInfo*, 4>;
fayang89b9af52019-07-23 07:52:49 -070086
QUICHE team18371262021-04-08 09:00:15 -070087 struct StreamInfo : public spdy::SpdyIntrusiveLink<StreamInfo> {
fayang89b9af52019-07-23 07:52:49 -070088 // ID for this stream.
89 StreamIdType id;
90 // StreamInfo for parent stream.
91 StreamInfo* parent = nullptr;
92 // Weights can range between 1 and 256 (inclusive).
QUICHE team18371262021-04-08 09:00:15 -070093 int weight = spdy::kHttp2DefaultStreamWeight;
fayang89b9af52019-07-23 07:52:49 -070094 // The total weight of this stream's direct descendants.
95 int total_child_weights = 0;
96 // Pointers to StreamInfos for children, if any.
97 StreamInfoVector children;
98 // Whether the stream is ready for writing. The stream is present in
99 // scheduling_queue_ iff true.
100 bool ready = false;
101 // The scheduling priority of this stream. Streams with higher priority
102 // values are scheduled first.
103 // TODO(mpw): rename to avoid confusion with SPDY priorities,
104 // which this is not.
105 float priority = 0;
106 // Ordinal value for this stream, used to ensure round-robin scheduling:
107 // among streams with the same scheduling priority, streams with lower
108 // ordinal are scheduled first.
109 int64_t ordinal = 0;
110 // Time of latest write event for stream of this priority, in microseconds.
111 int64_t last_event_time_usec = 0;
112
fayang86189502019-07-26 08:22:33 -0700113 // Returns true if this stream is ancestor of |other|.
114 bool IsAncestorOf(const StreamInfo& other) const {
fayangb8103922019-08-02 06:16:45 -0700115 for (const StreamInfo* p = other.parent; p != nullptr; p = p->parent) {
116 if (p == this) {
fayang86189502019-07-26 08:22:33 -0700117 return true;
118 }
119 }
120 return false;
121 }
122
fayang89b9af52019-07-23 07:52:49 -0700123 // Whether this stream should be scheduled ahead of another stream.
124 bool SchedulesBefore(const StreamInfo& other) const {
125 return (priority != other.priority) ? priority > other.priority
126 : ordinal < other.ordinal;
127 }
128
129 // Returns the StreamPrecedenceType for this StreamInfo.
130 StreamPrecedenceType ToStreamPrecedence() const {
131 StreamIdType parent_id =
QUICHE team18371262021-04-08 09:00:15 -0700132 parent == nullptr ? spdy::kHttp2RootStreamId : parent->id;
fayang89b9af52019-07-23 07:52:49 -0700133 bool exclusive = parent != nullptr && parent->children.size() == 1;
134 return StreamPrecedenceType(parent_id, weight, exclusive);
135 }
136 };
137
138 static bool Remove(StreamInfoVector* stream_infos,
139 const StreamInfo* stream_info);
140
141 // Returns true iff any direct or transitive parent of the given stream is
142 // currently ready.
143 static bool HasReadyAncestor(const StreamInfo& stream_info);
144
145 // Returns StreamInfo for the given stream, or nullptr if it isn't
146 // registered.
147 const StreamInfo* FindStream(StreamIdType stream_id) const;
148 StreamInfo* FindStream(StreamIdType stream_id);
149
150 // Helpers for UpdateStreamPrecedence().
151 void UpdateStreamParent(StreamInfo* stream_info,
152 StreamIdType parent_id,
153 bool exclusive);
154 void UpdateStreamWeight(StreamInfo* stream_info, int weight);
155
156 // Update all priority values in the subtree rooted at the given stream, not
157 // including the stream itself. If this results in priority value changes for
158 // scheduled streams, those streams are rescheduled to ensure proper ordering
159 // of scheduling_queue_.
160 // TODO(mpw): rename to avoid confusion with SPDY priorities.
161 void UpdatePrioritiesUnder(StreamInfo* stream_info);
162
163 // Inserts stream into scheduling_queue_ at the appropriate location given
164 // its priority and ordinal. Time complexity is O(scheduling_queue.size()).
165 void Schedule(StreamInfo* stream_info);
166
167 // Removes stream from scheduling_queue_.
168 void Unschedule(StreamInfo* stream_info);
169
170 // Return true if all internal invariants hold (useful for unit tests).
171 // Unless there are bugs, this should always return true.
172 bool ValidateInvariantsForTests() const;
173
174 // Returns true if the parent stream has the given stream in its children.
175 bool StreamHasChild(const StreamInfo& parent_info,
176 const StreamInfo* child_info) const;
177
178 // Pointee owned by all_stream_infos_.
179 StreamInfo* root_stream_info_;
180 // Maps from stream IDs to StreamInfo objects.
bnc93e89182021-04-29 10:03:53 -0400181 absl::flat_hash_map<StreamIdType, std::unique_ptr<StreamInfo>>
QUICHE team18371262021-04-08 09:00:15 -0700182 all_stream_infos_;
fayang89b9af52019-07-23 07:52:49 -0700183 // Queue containing all ready streams, ordered with streams of higher
184 // priority before streams of lower priority, and, among streams of equal
185 // priority, streams with lower ordinal before those with higher
186 // ordinal. Note that not all streams in scheduling_queue_ are eligible to be
187 // picked as the next stream: some may have ancestor stream(s) that are ready
188 // and unblocked. In these situations the occluded child streams are left in
189 // the queue, to reduce churn.
QUICHE team18371262021-04-08 09:00:15 -0700190 spdy::SpdyIntrusiveList<StreamInfo> scheduling_queue_;
fayang89b9af52019-07-23 07:52:49 -0700191 // Ordinal value to assign to next node inserted into scheduling_queue_ when
192 // |add_to_front == true|. Decremented after each assignment.
193 int64_t head_ordinal_ = -1;
194 // Ordinal value to assign to next node inserted into scheduling_queue_ when
195 // |add_to_front == false|. Incremented after each assignment.
196 int64_t tail_ordinal_ = 0;
197};
198
199template <typename StreamIdType>
200Http2PriorityWriteScheduler<StreamIdType>::Http2PriorityWriteScheduler() {
bnc463f2352019-10-10 04:49:34 -0700201 auto root_stream_info = std::make_unique<StreamInfo>();
fayang89b9af52019-07-23 07:52:49 -0700202 root_stream_info_ = root_stream_info.get();
QUICHE team18371262021-04-08 09:00:15 -0700203 root_stream_info->id = spdy::kHttp2RootStreamId;
204 root_stream_info->weight = spdy::kHttp2DefaultStreamWeight;
fayang89b9af52019-07-23 07:52:49 -0700205 root_stream_info->parent = nullptr;
206 root_stream_info->priority = 1.0;
207 root_stream_info->ready = false;
QUICHE team18371262021-04-08 09:00:15 -0700208 all_stream_infos_[spdy::kHttp2RootStreamId] = std::move(root_stream_info);
fayang89b9af52019-07-23 07:52:49 -0700209}
210
211template <typename StreamIdType>
fayang89b9af52019-07-23 07:52:49 -0700212bool Http2PriorityWriteScheduler<StreamIdType>::StreamRegistered(
213 StreamIdType stream_id) const {
vasilvvbbf16232020-09-16 08:53:15 -0700214 return all_stream_infos_.find(stream_id) != all_stream_infos_.end();
fayang89b9af52019-07-23 07:52:49 -0700215}
216
217template <typename StreamIdType>
218void Http2PriorityWriteScheduler<StreamIdType>::RegisterStream(
219 StreamIdType stream_id,
220 const StreamPrecedenceType& precedence) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700221 // TODO(mpw): uncomment the QUICHE_BUG_IF below once all tests
fayang89b9af52019-07-23 07:52:49 -0700222 // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
223 // appropriate for protocol version under test.
224 //
QUICHE team33a4aa62021-04-08 11:10:59 -0700225 // QUICHE_BUG_IF(spdy_bug_8_1, precedence.is_spdy3_priority())
fayang89b9af52019-07-23 07:52:49 -0700226 // << "Expected HTTP/2 stream dependency";
227
228 if (StreamRegistered(stream_id)) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700229 QUICHE_BUG(spdy_bug_8_2) << "Stream " << stream_id << " already registered";
fayang89b9af52019-07-23 07:52:49 -0700230 return;
231 }
232
233 StreamInfo* parent = FindStream(precedence.parent_id());
234 if (parent == nullptr) {
235 // parent_id may legitimately not be registered yet--see b/15676312.
QUICHE team1b5d09e2021-04-08 13:36:42 -0700236 QUICHE_VLOG(1) << "Parent stream " << precedence.parent_id()
237 << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700238 parent = root_stream_info_;
239 }
240
bnc463f2352019-10-10 04:49:34 -0700241 auto new_stream_info = std::make_unique<StreamInfo>();
fayang89b9af52019-07-23 07:52:49 -0700242 StreamInfo* new_stream_info_ptr = new_stream_info.get();
243 new_stream_info_ptr->id = stream_id;
244 new_stream_info_ptr->weight = precedence.weight();
245 new_stream_info_ptr->parent = parent;
246 all_stream_infos_[stream_id] = std::move(new_stream_info);
247 if (precedence.is_exclusive()) {
248 // Move the parent's current children below the new stream.
249 using std::swap;
250 swap(new_stream_info_ptr->children, parent->children);
251 new_stream_info_ptr->total_child_weights = parent->total_child_weights;
252 // Update each child's parent.
253 for (StreamInfo* child : new_stream_info_ptr->children) {
254 child->parent = new_stream_info_ptr;
255 }
256 // Clear parent's old child data.
vasilvved4f3082021-02-01 14:29:40 -0800257 QUICHE_DCHECK(parent->children.empty());
fayang89b9af52019-07-23 07:52:49 -0700258 parent->total_child_weights = 0;
259 }
260 // Add new stream to parent.
261 parent->children.push_back(new_stream_info_ptr);
262 parent->total_child_weights += precedence.weight();
263
264 // Update all priorities under parent, since addition of a stream affects
265 // sibling priorities as well.
266 UpdatePrioritiesUnder(parent);
267
268 // Stream starts with ready == false, so no need to schedule it yet.
vasilvved4f3082021-02-01 14:29:40 -0800269 QUICHE_DCHECK(!new_stream_info_ptr->ready);
fayang89b9af52019-07-23 07:52:49 -0700270}
271
272template <typename StreamIdType>
273void Http2PriorityWriteScheduler<StreamIdType>::UnregisterStream(
274 StreamIdType stream_id) {
QUICHE team18371262021-04-08 09:00:15 -0700275 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700276 QUICHE_BUG(spdy_bug_8_3) << "Cannot unregister root stream";
fayang89b9af52019-07-23 07:52:49 -0700277 return;
278 }
279 // Remove the stream from table.
280 auto it = all_stream_infos_.find(stream_id);
281 if (it == all_stream_infos_.end()) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700282 QUICHE_BUG(spdy_bug_8_4) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700283 return;
284 }
285 std::unique_ptr<StreamInfo> stream_info(std::move(it->second));
286 all_stream_infos_.erase(it);
287 // If ready (and hence scheduled), unschedule.
288 if (stream_info->ready) {
289 Unschedule(stream_info.get());
290 }
291
292 StreamInfo* parent = stream_info->parent;
293 // Remove the stream from parent's child list.
294 Remove(&parent->children, stream_info.get());
295 parent->total_child_weights -= stream_info->weight;
296
297 // Move the stream's children to the parent's child list.
298 // Update each child's parent and weight.
299 for (StreamInfo* child : stream_info->children) {
300 child->parent = parent;
301 parent->children.push_back(child);
302 // Divide the removed stream's weight among its children, rounding to the
303 // nearest valid weight.
304 float float_weight = stream_info->weight *
305 static_cast<float>(child->weight) /
306 static_cast<float>(stream_info->total_child_weights);
307 int new_weight = floor(float_weight + 0.5);
308 if (new_weight == 0) {
309 new_weight = 1;
310 }
311 child->weight = new_weight;
312 parent->total_child_weights += child->weight;
313 }
314 UpdatePrioritiesUnder(parent);
315}
316
317template <typename StreamIdType>
318typename Http2PriorityWriteScheduler<StreamIdType>::StreamPrecedenceType
319Http2PriorityWriteScheduler<StreamIdType>::GetStreamPrecedence(
320 StreamIdType stream_id) const {
321 const StreamInfo* stream_info = FindStream(stream_id);
322 if (stream_info == nullptr) {
323 // Unknown streams tolerated due to b/15676312. However, return lowest
324 // weight.
QUICHE team1b5d09e2021-04-08 13:36:42 -0700325 QUICHE_VLOG(1) << "Stream " << stream_id << " not registered";
QUICHE team18371262021-04-08 09:00:15 -0700326 return StreamPrecedenceType(spdy::kHttp2RootStreamId,
327 spdy::kHttp2MinStreamWeight, false);
fayang89b9af52019-07-23 07:52:49 -0700328 }
329 return stream_info->ToStreamPrecedence();
330}
331
332template <typename StreamIdType>
333std::vector<StreamIdType>
334Http2PriorityWriteScheduler<StreamIdType>::GetStreamChildren(
335 StreamIdType stream_id) const {
336 std::vector<StreamIdType> child_vec;
337 const StreamInfo* stream_info = FindStream(stream_id);
338 if (stream_info == nullptr) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700339 QUICHE_BUG(spdy_bug_8_5) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700340 } else {
341 child_vec.reserve(stream_info->children.size());
342 for (StreamInfo* child : stream_info->children) {
343 child_vec.push_back(child->id);
344 }
345 }
346 return child_vec;
347}
348
349template <typename StreamIdType>
350void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamPrecedence(
351 StreamIdType stream_id,
352 const StreamPrecedenceType& precedence) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700353 // TODO(mpw): uncomment the QUICHE_BUG_IF below once all tests
fayang89b9af52019-07-23 07:52:49 -0700354 // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
355 // appropriate for protocol version under test.
356 //
QUICHE team33a4aa62021-04-08 11:10:59 -0700357 // QUICHE_BUG_IF(spdy_bug_8_6, precedence.is_spdy3_priority())
fayang89b9af52019-07-23 07:52:49 -0700358 // << "Expected HTTP/2 stream dependency";
QUICHE team18371262021-04-08 09:00:15 -0700359 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700360 QUICHE_BUG(spdy_bug_8_7) << "Cannot set precedence of root stream";
fayang89b9af52019-07-23 07:52:49 -0700361 return;
362 }
363
364 StreamInfo* stream_info = FindStream(stream_id);
365 if (stream_info == nullptr) {
366 // TODO(mpw): add to all_stream_infos_ on demand--see b/15676312.
QUICHE team1b5d09e2021-04-08 13:36:42 -0700367 QUICHE_VLOG(1) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700368 return;
369 }
370 UpdateStreamParent(stream_info, precedence.parent_id(),
371 precedence.is_exclusive());
372 UpdateStreamWeight(stream_info, precedence.weight());
373}
374
375template <typename StreamIdType>
376void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamWeight(
377 StreamInfo* stream_info,
378 int weight) {
379 if (weight == stream_info->weight) {
380 return;
381 }
382 if (stream_info->parent != nullptr) {
383 stream_info->parent->total_child_weights += (weight - stream_info->weight);
384 }
385 stream_info->weight = weight;
386
387 // Change in weight also affects sibling priorities.
388 UpdatePrioritiesUnder(stream_info->parent);
389}
390
391template <typename StreamIdType>
392void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamParent(
393 StreamInfo* stream_info,
394 StreamIdType parent_id,
395 bool exclusive) {
396 if (stream_info->id == parent_id) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700397 QUICHE_BUG(spdy_bug_8_8) << "Cannot set stream to be its own parent";
fayang89b9af52019-07-23 07:52:49 -0700398 return;
399 }
400 StreamInfo* new_parent = FindStream(parent_id);
401 if (new_parent == nullptr) {
402 // parent_id may legitimately not be registered yet--see b/15676312.
QUICHE team1b5d09e2021-04-08 13:36:42 -0700403 QUICHE_VLOG(1) << "Parent stream " << parent_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700404 return;
405 }
406
fayang6cf80a52019-07-29 07:41:14 -0700407 if (stream_info->parent == new_parent &&
fayang67012992019-07-30 12:02:42 -0700408 (!exclusive || new_parent->children.size() == 1u)) {
fayang6cf80a52019-07-29 07:41:14 -0700409 // If the new parent is already the stream's parent, and exclusivity (if
410 // specified) is already satisfied, we are done.
fayang89b9af52019-07-23 07:52:49 -0700411 return;
412 }
413
414 // Next, check to see if the new parent is currently a descendant
415 // of the stream.
416 StreamInfo* last = new_parent->parent;
417 bool cycle_exists = false;
418 while (last != nullptr) {
419 if (last == stream_info) {
420 cycle_exists = true;
421 break;
422 }
423 last = last->parent;
424 }
425
426 if (cycle_exists) {
427 // The new parent moves to the level of the current stream.
428 UpdateStreamParent(new_parent, stream_info->parent->id, false);
429 }
430
431 // Remove stream from old parent's child list.
432 StreamInfo* old_parent = stream_info->parent;
433 Remove(&old_parent->children, stream_info);
434 old_parent->total_child_weights -= stream_info->weight;
435 UpdatePrioritiesUnder(old_parent);
436
437 if (exclusive) {
438 // Move the new parent's current children below the current stream.
439 for (StreamInfo* child : new_parent->children) {
440 child->parent = stream_info;
441 stream_info->children.push_back(child);
442 }
443 stream_info->total_child_weights += new_parent->total_child_weights;
444 // Clear new parent's old child data.
445 new_parent->children.clear();
446 new_parent->total_child_weights = 0;
447 }
448
449 // Make the change.
450 stream_info->parent = new_parent;
451 new_parent->children.push_back(stream_info);
452 new_parent->total_child_weights += stream_info->weight;
453 UpdatePrioritiesUnder(new_parent);
454}
455
456template <typename StreamIdType>
457void Http2PriorityWriteScheduler<StreamIdType>::RecordStreamEventTime(
458 StreamIdType stream_id,
459 int64_t now_in_usec) {
QUICHE team18371262021-04-08 09:00:15 -0700460 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700461 QUICHE_BUG(spdy_bug_8_9) << "Cannot record event time for root stream";
fayang89b9af52019-07-23 07:52:49 -0700462 return;
463 }
464 StreamInfo* stream_info = FindStream(stream_id);
465 if (stream_info == nullptr) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700466 QUICHE_BUG(spdy_bug_8_10) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700467 return;
468 }
469 stream_info->last_event_time_usec = now_in_usec;
470}
471
472// O(n) in the number of streams, which isn't great. However, this method will
473// soon be superseded by
474// Http2WeightedWriteScheduler::GetLatestEventWithPrecedence(), for which an
475// efficient implementation is straightforward. Also, this method is only
476// called when calculating idle timeouts, so performance isn't key.
477template <typename StreamIdType>
478int64_t Http2PriorityWriteScheduler<StreamIdType>::GetLatestEventWithPrecedence(
479 StreamIdType stream_id) const {
QUICHE team18371262021-04-08 09:00:15 -0700480 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700481 QUICHE_BUG(spdy_bug_8_11) << "Invalid argument: root stream";
fayang89b9af52019-07-23 07:52:49 -0700482 return 0;
483 }
484 const StreamInfo* stream_info = FindStream(stream_id);
485 if (stream_info == nullptr) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700486 QUICHE_BUG(spdy_bug_8_12) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700487 return 0;
488 }
489 int64_t last_event_time_usec = 0;
490 for (const auto& kv : all_stream_infos_) {
491 const StreamInfo& other = *kv.second;
492 if (other.priority > stream_info->priority) {
493 last_event_time_usec =
494 std::max(last_event_time_usec, other.last_event_time_usec);
495 }
496 }
497 return last_event_time_usec;
498}
499
500// Worst-case time complexity of O(n*d), where n is scheduling queue length and
501// d is tree depth. In practice, should be much shorter, since loop terminates
502// at first writable stream or |stream_id| (whichever is first).
503template <typename StreamIdType>
504bool Http2PriorityWriteScheduler<StreamIdType>::ShouldYield(
505 StreamIdType stream_id) const {
QUICHE team18371262021-04-08 09:00:15 -0700506 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700507 QUICHE_BUG(spdy_bug_8_13) << "Invalid argument: root stream";
fayang89b9af52019-07-23 07:52:49 -0700508 return false;
509 }
510 const StreamInfo* stream_info = FindStream(stream_id);
511 if (stream_info == nullptr) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700512 QUICHE_BUG(spdy_bug_8_14) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700513 return false;
514 }
fayang86189502019-07-26 08:22:33 -0700515 if (HasReadyAncestor(*stream_info)) {
516 return true;
517 }
fayang89b9af52019-07-23 07:52:49 -0700518 for (const StreamInfo& scheduled : scheduling_queue_) {
fayang86189502019-07-26 08:22:33 -0700519 if (HasReadyAncestor(scheduled)) {
520 // Skip streams which cannot be scheduled.
521 continue;
522 }
523 if (stream_info->IsAncestorOf(scheduled)) {
524 // Do not yield to descendants.
fayang89b9af52019-07-23 07:52:49 -0700525 return false;
526 }
fayang86189502019-07-26 08:22:33 -0700527 // Yield to streams with higher priorities.
528 return scheduled.SchedulesBefore(*stream_info);
fayang89b9af52019-07-23 07:52:49 -0700529 }
530 return false;
531}
532
533template <typename StreamIdType>
534void Http2PriorityWriteScheduler<StreamIdType>::MarkStreamReady(
535 StreamIdType stream_id,
536 bool add_to_front) {
QUICHE team18371262021-04-08 09:00:15 -0700537 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700538 QUICHE_BUG(spdy_bug_8_15) << "Cannot mark root stream ready";
fayang89b9af52019-07-23 07:52:49 -0700539 return;
540 }
541 StreamInfo* stream_info = FindStream(stream_id);
542 if (stream_info == nullptr) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700543 QUICHE_BUG(spdy_bug_8_16) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700544 return;
545 }
546 if (stream_info->ready) {
547 return;
548 }
549 stream_info->ordinal = add_to_front ? head_ordinal_-- : tail_ordinal_++;
550 Schedule(stream_info);
551}
552
553template <typename StreamIdType>
554void Http2PriorityWriteScheduler<StreamIdType>::MarkStreamNotReady(
555 StreamIdType stream_id) {
QUICHE team18371262021-04-08 09:00:15 -0700556 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700557 QUICHE_BUG(spdy_bug_8_17) << "Cannot mark root stream unready";
fayang89b9af52019-07-23 07:52:49 -0700558 return;
559 }
560 StreamInfo* stream_info = FindStream(stream_id);
561 if (stream_info == nullptr) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700562 QUICHE_BUG(spdy_bug_8_18) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700563 return;
564 }
565 if (!stream_info->ready) {
566 return;
567 }
568 Unschedule(stream_info);
569}
570
571template <typename StreamIdType>
572bool Http2PriorityWriteScheduler<StreamIdType>::Remove(
573 StreamInfoVector* stream_infos,
574 const StreamInfo* stream_info) {
575 for (typename StreamInfoVector::iterator it = stream_infos->begin();
576 it != stream_infos->end(); ++it) {
577 if (*it == stream_info) {
578 stream_infos->erase(it);
579 return true;
580 }
581 }
582 return false;
583}
584
585template <typename StreamIdType>
586bool Http2PriorityWriteScheduler<StreamIdType>::HasReadyAncestor(
587 const StreamInfo& stream_info) {
588 for (const StreamInfo* parent = stream_info.parent; parent != nullptr;
589 parent = parent->parent) {
590 if (parent->ready) {
591 return true;
592 }
593 }
594 return false;
595}
596
597template <typename StreamIdType>
598const typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
599Http2PriorityWriteScheduler<StreamIdType>::FindStream(
600 StreamIdType stream_id) const {
601 auto it = all_stream_infos_.find(stream_id);
602 return it == all_stream_infos_.end() ? nullptr : it->second.get();
603}
604
605template <typename StreamIdType>
606typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
607Http2PriorityWriteScheduler<StreamIdType>::FindStream(StreamIdType stream_id) {
608 auto it = all_stream_infos_.find(stream_id);
609 return it == all_stream_infos_.end() ? nullptr : it->second.get();
610}
611
612template <typename StreamIdType>
613void Http2PriorityWriteScheduler<StreamIdType>::UpdatePrioritiesUnder(
614 StreamInfo* stream_info) {
615 for (StreamInfo* child : stream_info->children) {
616 child->priority = stream_info->priority *
617 (static_cast<float>(child->weight) /
618 static_cast<float>(stream_info->total_child_weights));
619 if (child->ready) {
620 // Reposition in scheduling_queue_. Use post-order for scheduling, to
621 // benefit from the fact that children have priority <= parent priority.
622 Unschedule(child);
623 UpdatePrioritiesUnder(child);
624 Schedule(child);
625 } else {
626 UpdatePrioritiesUnder(child);
627 }
628 }
629}
630
631template <typename StreamIdType>
632void Http2PriorityWriteScheduler<StreamIdType>::Schedule(
633 StreamInfo* stream_info) {
vasilvved4f3082021-02-01 14:29:40 -0800634 QUICHE_DCHECK(!stream_info->ready);
fayang89b9af52019-07-23 07:52:49 -0700635 for (StreamInfo& s : scheduling_queue_) {
636 if (stream_info->SchedulesBefore(s)) {
637 scheduling_queue_.insert(&s, stream_info);
638 stream_info->ready = true;
639 return;
640 }
641 }
642 scheduling_queue_.push_back(stream_info);
643 stream_info->ready = true;
644}
645
646template <typename StreamIdType>
647void Http2PriorityWriteScheduler<StreamIdType>::Unschedule(
648 StreamInfo* stream_info) {
vasilvved4f3082021-02-01 14:29:40 -0800649 QUICHE_DCHECK(stream_info->ready);
fayang89b9af52019-07-23 07:52:49 -0700650 scheduling_queue_.erase(stream_info);
651 stream_info->ready = false;
652}
653
654template <typename StreamIdType>
655bool Http2PriorityWriteScheduler<StreamIdType>::StreamHasChild(
656 const StreamInfo& parent_info,
657 const StreamInfo* child_info) const {
658 auto found = std::find(parent_info.children.begin(),
659 parent_info.children.end(), child_info);
660 return found != parent_info.children.end();
661}
662
663template <typename StreamIdType>
664bool Http2PriorityWriteScheduler<StreamIdType>::HasReadyStreams() const {
665 return !scheduling_queue_.empty();
666}
667
668template <typename StreamIdType>
669StreamIdType Http2PriorityWriteScheduler<StreamIdType>::PopNextReadyStream() {
670 return std::get<0>(PopNextReadyStreamAndPrecedence());
671}
672
673template <typename StreamIdType>
674std::tuple<
675 StreamIdType,
676 typename Http2PriorityWriteScheduler<StreamIdType>::StreamPrecedenceType>
677Http2PriorityWriteScheduler<StreamIdType>::PopNextReadyStreamAndPrecedence() {
678 for (StreamInfo& stream_info : scheduling_queue_) {
679 if (!HasReadyAncestor(stream_info)) {
680 Unschedule(&stream_info);
681 return std::make_tuple(stream_info.id, stream_info.ToStreamPrecedence());
682 }
683 }
QUICHE team33a4aa62021-04-08 11:10:59 -0700684 QUICHE_BUG(spdy_bug_8_19) << "No ready streams";
fayang89b9af52019-07-23 07:52:49 -0700685 return std::make_tuple(
QUICHE team18371262021-04-08 09:00:15 -0700686 spdy::kHttp2RootStreamId,
687 StreamPrecedenceType(spdy::kHttp2RootStreamId,
688 spdy::kHttp2MinStreamWeight, false));
fayang89b9af52019-07-23 07:52:49 -0700689}
690
691template <typename StreamIdType>
692size_t Http2PriorityWriteScheduler<StreamIdType>::NumReadyStreams() const {
693 return scheduling_queue_.size();
694}
695
696template <typename StreamIdType>
697bool Http2PriorityWriteScheduler<StreamIdType>::IsStreamReady(
698 StreamIdType stream_id) const {
QUICHE team18371262021-04-08 09:00:15 -0700699 if (stream_id == spdy::kHttp2RootStreamId) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700700 QUICHE_BUG(spdy_bug_8_20) << "Try to check whether root stream is ready";
fayang89b9af52019-07-23 07:52:49 -0700701 return false;
702 }
703 const StreamInfo* stream_info = FindStream(stream_id);
704 if (stream_info == nullptr) {
QUICHE team33a4aa62021-04-08 11:10:59 -0700705 QUICHE_BUG(spdy_bug_8_21) << "Stream " << stream_id << " not registered";
fayang89b9af52019-07-23 07:52:49 -0700706 return false;
707 }
708 return stream_info->ready;
709}
710
711template <typename StreamIdType>
fayang753002d2019-07-24 11:54:43 -0700712size_t Http2PriorityWriteScheduler<StreamIdType>::NumRegisteredStreams() const {
713 return all_stream_infos_.size();
714}
715
716template <typename StreamIdType>
bnc44712912019-08-15 18:58:14 -0700717std::string Http2PriorityWriteScheduler<StreamIdType>::DebugString() const {
vasilvv1ea0b542020-12-03 15:21:00 -0800718 return absl::StrCat("Http2PriorityWriteScheduler {num_registered_streams=",
719 NumRegisteredStreams(),
720 " num_ready_streams=", NumReadyStreams(), "}");
fayang89b9af52019-07-23 07:52:49 -0700721}
722
723template <typename StreamIdType>
724bool Http2PriorityWriteScheduler<StreamIdType>::ValidateInvariantsForTests()
725 const {
fayang67012992019-07-30 12:02:42 -0700726 size_t total_streams = 0;
727 size_t streams_visited = 0;
fayang89b9af52019-07-23 07:52:49 -0700728 // Iterate through all streams in the map.
729 for (const auto& kv : all_stream_infos_) {
730 ++total_streams;
731 ++streams_visited;
732 StreamIdType stream_id = kv.first;
733 const StreamInfo& stream_info = *kv.second;
734
735 // Verify each StreamInfo mapped under the proper stream ID.
736 if (stream_id != stream_info.id) {
QUICHE team1b5d09e2021-04-08 13:36:42 -0700737 QUICHE_DLOG(INFO) << "Stream ID " << stream_id
738 << " maps to StreamInfo with ID " << stream_info.id;
fayang89b9af52019-07-23 07:52:49 -0700739 return false;
740 }
741
742 // All streams except the root should have a parent, and should appear in
743 // the children of that parent.
QUICHE team18371262021-04-08 09:00:15 -0700744 if (stream_info.id != spdy::kHttp2RootStreamId &&
fayang89b9af52019-07-23 07:52:49 -0700745 !StreamHasChild(*stream_info.parent, &stream_info)) {
QUICHE team1b5d09e2021-04-08 13:36:42 -0700746 QUICHE_DLOG(INFO) << "Parent stream " << stream_info.parent->id
747 << " is not registered, or does not list stream "
748 << stream_info.id << " as its child.";
fayang89b9af52019-07-23 07:52:49 -0700749 return false;
750 }
751
752 if (!stream_info.children.empty()) {
753 int total_child_weights = 0;
754 // Iterate through the stream's children.
755 for (StreamInfo* child : stream_info.children) {
756 ++streams_visited;
757 // Each stream in the list should exist and should have this stream
758 // set as its parent.
759 if (!StreamRegistered(child->id) || child->parent != &stream_info) {
QUICHE team1b5d09e2021-04-08 13:36:42 -0700760 QUICHE_DLOG(INFO)
761 << "Child stream " << child->id << " is not registered, "
762 << "or does not list " << stream_info.id << " as its parent.";
fayang89b9af52019-07-23 07:52:49 -0700763 return false;
764 }
765 total_child_weights += child->weight;
766 }
767 // Verify that total_child_weights is correct.
768 if (total_child_weights != stream_info.total_child_weights) {
QUICHE team1b5d09e2021-04-08 13:36:42 -0700769 QUICHE_DLOG(INFO) << "Child weight totals do not agree. For stream "
770 << stream_info.id << " total_child_weights has value "
771 << stream_info.total_child_weights << ", expected "
772 << total_child_weights;
fayang89b9af52019-07-23 07:52:49 -0700773 return false;
774 }
775 }
776 }
777
fayang753002d2019-07-24 11:54:43 -0700778 // Make sure NumRegisteredStreams() reflects the total number of streams the
779 // map contains.
780 if (total_streams != NumRegisteredStreams()) {
QUICHE team1b5d09e2021-04-08 13:36:42 -0700781 QUICHE_DLOG(INFO) << "Map contains incorrect number of streams.";
fayang89b9af52019-07-23 07:52:49 -0700782 return false;
783 }
784 // Validate the validation function; we should have visited each stream twice
785 // (except for the root)
vasilvved4f3082021-02-01 14:29:40 -0800786 QUICHE_DCHECK(streams_visited == 2 * NumRegisteredStreams() - 1);
fayang89b9af52019-07-23 07:52:49 -0700787 return true;
788}
789
QUICHE team18371262021-04-08 09:00:15 -0700790} // namespace http2
fayang89b9af52019-07-23 07:52:49 -0700791
QUICHE team18371262021-04-08 09:00:15 -0700792#endif // QUICHE_HTTP2_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_