blob: 125583926c1385758bdc24068d642c08d60b67c2 [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
5#ifndef QUICHE_SPDY_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_
6#define QUICHE_SPDY_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_
7
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
20#include "net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h"
21#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
22#include "net/third_party/quiche/src/spdy/core/write_scheduler.h"
23#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
24#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
25#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
26#include "net/third_party/quiche/src/spdy/platform/api/spdy_map_util.h"
27#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
fayang89b9af52019-07-23 07:52:49 -070028#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
29
30namespace spdy {
31
32namespace test {
33template <typename StreamIdType>
34class Http2PriorityWriteSchedulerPeer;
35}
36
37// This data structure implements the HTTP/2 stream priority tree defined in
38// section 5.3 of RFC 7540:
39// http://tools.ietf.org/html/rfc7540#section-5.3
40//
41// Streams can be added and removed, and dependencies between them defined.
42// Streams constitute a tree rooted at stream ID 0: each stream has a single
43// parent stream, and 0 or more child streams. Individual streams can be
44// marked as ready to read/write, and then the whole structure can be queried
45// to pick the next stream to read/write out of those that are ready.
46template <typename StreamIdType>
47class Http2PriorityWriteScheduler : public WriteScheduler<StreamIdType> {
48 public:
49 using typename WriteScheduler<StreamIdType>::StreamPrecedenceType;
50
51 Http2PriorityWriteScheduler();
52 Http2PriorityWriteScheduler(const Http2PriorityWriteScheduler&) = delete;
53 Http2PriorityWriteScheduler& operator=(const Http2PriorityWriteScheduler&) =
54 delete;
55
56 // WriteScheduler methods
57 void RegisterStream(StreamIdType stream_id,
58 const StreamPrecedenceType& precedence) override;
59 void UnregisterStream(StreamIdType stream_id) override;
60 bool StreamRegistered(StreamIdType stream_id) const override;
61 StreamPrecedenceType GetStreamPrecedence(
62 StreamIdType stream_id) const override;
63 void UpdateStreamPrecedence(StreamIdType stream_id,
64 const StreamPrecedenceType& precedence) override;
65 std::vector<StreamIdType> GetStreamChildren(
66 StreamIdType stream_id) const override;
67 void RecordStreamEventTime(StreamIdType stream_id,
68 int64_t now_in_usec) override;
69 int64_t GetLatestEventWithPrecedence(StreamIdType stream_id) const override;
70 bool ShouldYield(StreamIdType stream_id) const override;
71 void MarkStreamReady(StreamIdType stream_id, bool add_to_front) override;
72 void MarkStreamNotReady(StreamIdType stream_id) override;
73 bool HasReadyStreams() const override;
74 StreamIdType PopNextReadyStream() override;
75 std::tuple<StreamIdType, StreamPrecedenceType>
76 PopNextReadyStreamAndPrecedence() override;
77 size_t NumReadyStreams() const override;
78 bool IsStreamReady(StreamIdType stream_id) const override;
fayang753002d2019-07-24 11:54:43 -070079 size_t NumRegisteredStreams() const override;
bnc44712912019-08-15 18:58:14 -070080 std::string DebugString() const override;
fayang89b9af52019-07-23 07:52:49 -070081
fayang89b9af52019-07-23 07:52:49 -070082 private:
83 friend class test::Http2PriorityWriteSchedulerPeer<StreamIdType>;
84
85 struct StreamInfo;
86 typedef SpdyInlinedVector<StreamInfo*, 4> StreamInfoVector;
87
88 struct StreamInfo : public SpdyIntrusiveLink<StreamInfo> {
89 // ID for this stream.
90 StreamIdType id;
91 // StreamInfo for parent stream.
92 StreamInfo* parent = nullptr;
93 // Weights can range between 1 and 256 (inclusive).
94 int weight = kHttp2DefaultStreamWeight;
95 // The total weight of this stream's direct descendants.
96 int total_child_weights = 0;
97 // Pointers to StreamInfos for children, if any.
98 StreamInfoVector children;
99 // Whether the stream is ready for writing. The stream is present in
100 // scheduling_queue_ iff true.
101 bool ready = false;
102 // The scheduling priority of this stream. Streams with higher priority
103 // values are scheduled first.
104 // TODO(mpw): rename to avoid confusion with SPDY priorities,
105 // which this is not.
106 float priority = 0;
107 // Ordinal value for this stream, used to ensure round-robin scheduling:
108 // among streams with the same scheduling priority, streams with lower
109 // ordinal are scheduled first.
110 int64_t ordinal = 0;
111 // Time of latest write event for stream of this priority, in microseconds.
112 int64_t last_event_time_usec = 0;
113
fayang86189502019-07-26 08:22:33 -0700114 // Returns true if this stream is ancestor of |other|.
115 bool IsAncestorOf(const StreamInfo& other) const {
fayangb8103922019-08-02 06:16:45 -0700116 for (const StreamInfo* p = other.parent; p != nullptr; p = p->parent) {
117 if (p == this) {
fayang86189502019-07-26 08:22:33 -0700118 return true;
119 }
120 }
121 return false;
122 }
123
fayang89b9af52019-07-23 07:52:49 -0700124 // Whether this stream should be scheduled ahead of another stream.
125 bool SchedulesBefore(const StreamInfo& other) const {
126 return (priority != other.priority) ? priority > other.priority
127 : ordinal < other.ordinal;
128 }
129
130 // Returns the StreamPrecedenceType for this StreamInfo.
131 StreamPrecedenceType ToStreamPrecedence() const {
132 StreamIdType parent_id =
133 parent == nullptr ? kHttp2RootStreamId : parent->id;
134 bool exclusive = parent != nullptr && parent->children.size() == 1;
135 return StreamPrecedenceType(parent_id, weight, exclusive);
136 }
137 };
138
139 static bool Remove(StreamInfoVector* stream_infos,
140 const StreamInfo* stream_info);
141
142 // Returns true iff any direct or transitive parent of the given stream is
143 // currently ready.
144 static bool HasReadyAncestor(const StreamInfo& stream_info);
145
146 // Returns StreamInfo for the given stream, or nullptr if it isn't
147 // registered.
148 const StreamInfo* FindStream(StreamIdType stream_id) const;
149 StreamInfo* FindStream(StreamIdType stream_id);
150
151 // Helpers for UpdateStreamPrecedence().
152 void UpdateStreamParent(StreamInfo* stream_info,
153 StreamIdType parent_id,
154 bool exclusive);
155 void UpdateStreamWeight(StreamInfo* stream_info, int weight);
156
157 // Update all priority values in the subtree rooted at the given stream, not
158 // including the stream itself. If this results in priority value changes for
159 // scheduled streams, those streams are rescheduled to ensure proper ordering
160 // of scheduling_queue_.
161 // TODO(mpw): rename to avoid confusion with SPDY priorities.
162 void UpdatePrioritiesUnder(StreamInfo* stream_info);
163
164 // Inserts stream into scheduling_queue_ at the appropriate location given
165 // its priority and ordinal. Time complexity is O(scheduling_queue.size()).
166 void Schedule(StreamInfo* stream_info);
167
168 // Removes stream from scheduling_queue_.
169 void Unschedule(StreamInfo* stream_info);
170
171 // Return true if all internal invariants hold (useful for unit tests).
172 // Unless there are bugs, this should always return true.
173 bool ValidateInvariantsForTests() const;
174
175 // Returns true if the parent stream has the given stream in its children.
176 bool StreamHasChild(const StreamInfo& parent_info,
177 const StreamInfo* child_info) const;
178
179 // Pointee owned by all_stream_infos_.
180 StreamInfo* root_stream_info_;
181 // Maps from stream IDs to StreamInfo objects.
182 SpdySmallMap<StreamIdType, std::unique_ptr<StreamInfo>, 10> all_stream_infos_;
183 // 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.
190 SpdyIntrusiveList<StreamInfo> scheduling_queue_;
191 // 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() {
201 auto root_stream_info = SpdyMakeUnique<StreamInfo>();
202 root_stream_info_ = root_stream_info.get();
203 root_stream_info->id = kHttp2RootStreamId;
204 root_stream_info->weight = kHttp2DefaultStreamWeight;
205 root_stream_info->parent = nullptr;
206 root_stream_info->priority = 1.0;
207 root_stream_info->ready = false;
208 all_stream_infos_[kHttp2RootStreamId] = std::move(root_stream_info);
209}
210
211template <typename StreamIdType>
fayang89b9af52019-07-23 07:52:49 -0700212bool Http2PriorityWriteScheduler<StreamIdType>::StreamRegistered(
213 StreamIdType stream_id) const {
214 return SpdyContainsKey(all_stream_infos_, stream_id);
215}
216
217template <typename StreamIdType>
218void Http2PriorityWriteScheduler<StreamIdType>::RegisterStream(
219 StreamIdType stream_id,
220 const StreamPrecedenceType& precedence) {
221 // TODO(mpw): uncomment the SPDY_BUG_IF below once all tests
222 // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
223 // appropriate for protocol version under test.
224 //
225 // SPDY_BUG_IF(precedence.is_spdy3_priority())
226 // << "Expected HTTP/2 stream dependency";
227
228 if (StreamRegistered(stream_id)) {
229 SPDY_BUG << "Stream " << stream_id << " already registered";
230 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.
236 SPDY_VLOG(1) << "Parent stream " << precedence.parent_id()
237 << " not registered";
238 parent = root_stream_info_;
239 }
240
241 auto new_stream_info = SpdyMakeUnique<StreamInfo>();
242 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.
257 DCHECK(parent->children.empty());
258 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.
269 DCHECK(!new_stream_info_ptr->ready);
270}
271
272template <typename StreamIdType>
273void Http2PriorityWriteScheduler<StreamIdType>::UnregisterStream(
274 StreamIdType stream_id) {
275 if (stream_id == kHttp2RootStreamId) {
276 SPDY_BUG << "Cannot unregister root stream";
277 return;
278 }
279 // Remove the stream from table.
280 auto it = all_stream_infos_.find(stream_id);
281 if (it == all_stream_infos_.end()) {
282 SPDY_BUG << "Stream " << stream_id << " not registered";
283 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.
325 SPDY_VLOG(1) << "Stream " << stream_id << " not registered";
326 return StreamPrecedenceType(kHttp2RootStreamId, kHttp2MinStreamWeight,
327 false);
328 }
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) {
339 SPDY_BUG << "Stream " << stream_id << " not registered";
340 } 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) {
353 // TODO(mpw): uncomment the SPDY_BUG_IF below once all tests
354 // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
355 // appropriate for protocol version under test.
356 //
357 // SPDY_BUG_IF(precedence.is_spdy3_priority())
358 // << "Expected HTTP/2 stream dependency";
359 if (stream_id == kHttp2RootStreamId) {
360 SPDY_BUG << "Cannot set precedence of root stream";
361 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.
367 SPDY_VLOG(1) << "Stream " << stream_id << " not registered";
368 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) {
397 SPDY_BUG << "Cannot set stream to be its own parent";
398 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.
403 SPDY_VLOG(1) << "Parent stream " << parent_id << " not registered";
404 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) {
460 if (stream_id == kHttp2RootStreamId) {
461 SPDY_BUG << "Cannot record event time for root stream";
462 return;
463 }
464 StreamInfo* stream_info = FindStream(stream_id);
465 if (stream_info == nullptr) {
466 SPDY_BUG << "Stream " << stream_id << " not registered";
467 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 {
480 if (stream_id == kHttp2RootStreamId) {
481 SPDY_BUG << "Invalid argument: root stream";
482 return 0;
483 }
484 const StreamInfo* stream_info = FindStream(stream_id);
485 if (stream_info == nullptr) {
486 SPDY_BUG << "Stream " << stream_id << " not registered";
487 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 {
506 if (stream_id == kHttp2RootStreamId) {
507 SPDY_BUG << "Invalid argument: root stream";
508 return false;
509 }
510 const StreamInfo* stream_info = FindStream(stream_id);
511 if (stream_info == nullptr) {
512 SPDY_BUG << "Stream " << stream_id << " not registered";
513 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) {
537 if (stream_id == kHttp2RootStreamId) {
538 SPDY_BUG << "Cannot mark root stream ready";
539 return;
540 }
541 StreamInfo* stream_info = FindStream(stream_id);
542 if (stream_info == nullptr) {
543 SPDY_BUG << "Stream " << stream_id << " not registered";
544 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) {
556 if (stream_id == kHttp2RootStreamId) {
557 SPDY_BUG << "Cannot mark root stream unready";
558 return;
559 }
560 StreamInfo* stream_info = FindStream(stream_id);
561 if (stream_info == nullptr) {
562 SPDY_BUG << "Stream " << stream_id << " not registered";
563 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) {
634 DCHECK(!stream_info->ready);
635 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) {
649 DCHECK(stream_info->ready);
650 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 }
684 SPDY_BUG << "No ready streams";
685 return std::make_tuple(
686 kHttp2RootStreamId,
687 StreamPrecedenceType(kHttp2RootStreamId, kHttp2MinStreamWeight, false));
688}
689
690template <typename StreamIdType>
691size_t Http2PriorityWriteScheduler<StreamIdType>::NumReadyStreams() const {
692 return scheduling_queue_.size();
693}
694
695template <typename StreamIdType>
696bool Http2PriorityWriteScheduler<StreamIdType>::IsStreamReady(
697 StreamIdType stream_id) const {
698 if (stream_id == kHttp2RootStreamId) {
699 SPDY_BUG << "Try to check whether root stream is ready";
700 return false;
701 }
702 const StreamInfo* stream_info = FindStream(stream_id);
703 if (stream_info == nullptr) {
704 SPDY_BUG << "Stream " << stream_id << " not registered";
705 return false;
706 }
707 return stream_info->ready;
708}
709
710template <typename StreamIdType>
fayang753002d2019-07-24 11:54:43 -0700711size_t Http2PriorityWriteScheduler<StreamIdType>::NumRegisteredStreams() const {
712 return all_stream_infos_.size();
713}
714
715template <typename StreamIdType>
bnc44712912019-08-15 18:58:14 -0700716std::string Http2PriorityWriteScheduler<StreamIdType>::DebugString() const {
fayang753002d2019-07-24 11:54:43 -0700717 return SpdyStrCat("Http2PriorityWriteScheduler {num_registered_streams=",
718 NumRegisteredStreams(),
fayang89b9af52019-07-23 07:52:49 -0700719 " num_ready_streams=", NumReadyStreams(), "}");
720}
721
722template <typename StreamIdType>
723bool Http2PriorityWriteScheduler<StreamIdType>::ValidateInvariantsForTests()
724 const {
fayang67012992019-07-30 12:02:42 -0700725 size_t total_streams = 0;
726 size_t streams_visited = 0;
fayang89b9af52019-07-23 07:52:49 -0700727 // Iterate through all streams in the map.
728 for (const auto& kv : all_stream_infos_) {
729 ++total_streams;
730 ++streams_visited;
731 StreamIdType stream_id = kv.first;
732 const StreamInfo& stream_info = *kv.second;
733
734 // Verify each StreamInfo mapped under the proper stream ID.
735 if (stream_id != stream_info.id) {
736 SPDY_DLOG(INFO) << "Stream ID " << stream_id
737 << " maps to StreamInfo with ID " << stream_info.id;
738 return false;
739 }
740
741 // All streams except the root should have a parent, and should appear in
742 // the children of that parent.
743 if (stream_info.id != kHttp2RootStreamId &&
744 !StreamHasChild(*stream_info.parent, &stream_info)) {
745 SPDY_DLOG(INFO) << "Parent stream " << stream_info.parent->id
746 << " is not registered, or does not list stream "
747 << stream_info.id << " as its child.";
748 return false;
749 }
750
751 if (!stream_info.children.empty()) {
752 int total_child_weights = 0;
753 // Iterate through the stream's children.
754 for (StreamInfo* child : stream_info.children) {
755 ++streams_visited;
756 // Each stream in the list should exist and should have this stream
757 // set as its parent.
758 if (!StreamRegistered(child->id) || child->parent != &stream_info) {
759 SPDY_DLOG(INFO) << "Child stream " << child->id
760 << " is not registered, "
761 << "or does not list " << stream_info.id
762 << " as its parent.";
763 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) {
769 SPDY_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;
773 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()) {
fayang89b9af52019-07-23 07:52:49 -0700781 SPDY_DLOG(INFO) << "Map contains incorrect number of streams.";
782 return false;
783 }
784 // Validate the validation function; we should have visited each stream twice
785 // (except for the root)
fayang753002d2019-07-24 11:54:43 -0700786 DCHECK(streams_visited == 2 * NumRegisteredStreams() - 1);
fayang89b9af52019-07-23 07:52:49 -0700787 return true;
788}
789
790} // namespace spdy
791
792#endif // QUICHE_SPDY_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_