blob: d08d125f9332df7b12864e22a022533dfc0f6ecd [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>
14#include <tuple>
15#include <unordered_map>
16#include <utility>
17#include <vector>
18
19#include "net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h"
20#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
21#include "net/third_party/quiche/src/spdy/core/write_scheduler.h"
22#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
23#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
24#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
25#include "net/third_party/quiche/src/spdy/platform/api/spdy_map_util.h"
26#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
27#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
28#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;
fayang89b9af52019-07-23 07:52:49 -070080 SpdyString DebugString() const override;
81
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
114 // Whether this stream should be scheduled ahead of another stream.
115 bool SchedulesBefore(const StreamInfo& other) const {
116 return (priority != other.priority) ? priority > other.priority
117 : ordinal < other.ordinal;
118 }
119
120 // Returns the StreamPrecedenceType for this StreamInfo.
121 StreamPrecedenceType ToStreamPrecedence() const {
122 StreamIdType parent_id =
123 parent == nullptr ? kHttp2RootStreamId : parent->id;
124 bool exclusive = parent != nullptr && parent->children.size() == 1;
125 return StreamPrecedenceType(parent_id, weight, exclusive);
126 }
127 };
128
129 static bool Remove(StreamInfoVector* stream_infos,
130 const StreamInfo* stream_info);
131
132 // Returns true iff any direct or transitive parent of the given stream is
133 // currently ready.
134 static bool HasReadyAncestor(const StreamInfo& stream_info);
135
136 // Returns StreamInfo for the given stream, or nullptr if it isn't
137 // registered.
138 const StreamInfo* FindStream(StreamIdType stream_id) const;
139 StreamInfo* FindStream(StreamIdType stream_id);
140
141 // Helpers for UpdateStreamPrecedence().
142 void UpdateStreamParent(StreamInfo* stream_info,
143 StreamIdType parent_id,
144 bool exclusive);
145 void UpdateStreamWeight(StreamInfo* stream_info, int weight);
146
147 // Update all priority values in the subtree rooted at the given stream, not
148 // including the stream itself. If this results in priority value changes for
149 // scheduled streams, those streams are rescheduled to ensure proper ordering
150 // of scheduling_queue_.
151 // TODO(mpw): rename to avoid confusion with SPDY priorities.
152 void UpdatePrioritiesUnder(StreamInfo* stream_info);
153
154 // Inserts stream into scheduling_queue_ at the appropriate location given
155 // its priority and ordinal. Time complexity is O(scheduling_queue.size()).
156 void Schedule(StreamInfo* stream_info);
157
158 // Removes stream from scheduling_queue_.
159 void Unschedule(StreamInfo* stream_info);
160
161 // Return true if all internal invariants hold (useful for unit tests).
162 // Unless there are bugs, this should always return true.
163 bool ValidateInvariantsForTests() const;
164
165 // Returns true if the parent stream has the given stream in its children.
166 bool StreamHasChild(const StreamInfo& parent_info,
167 const StreamInfo* child_info) const;
168
169 // Pointee owned by all_stream_infos_.
170 StreamInfo* root_stream_info_;
171 // Maps from stream IDs to StreamInfo objects.
172 SpdySmallMap<StreamIdType, std::unique_ptr<StreamInfo>, 10> all_stream_infos_;
173 // Queue containing all ready streams, ordered with streams of higher
174 // priority before streams of lower priority, and, among streams of equal
175 // priority, streams with lower ordinal before those with higher
176 // ordinal. Note that not all streams in scheduling_queue_ are eligible to be
177 // picked as the next stream: some may have ancestor stream(s) that are ready
178 // and unblocked. In these situations the occluded child streams are left in
179 // the queue, to reduce churn.
180 SpdyIntrusiveList<StreamInfo> scheduling_queue_;
181 // Ordinal value to assign to next node inserted into scheduling_queue_ when
182 // |add_to_front == true|. Decremented after each assignment.
183 int64_t head_ordinal_ = -1;
184 // Ordinal value to assign to next node inserted into scheduling_queue_ when
185 // |add_to_front == false|. Incremented after each assignment.
186 int64_t tail_ordinal_ = 0;
187};
188
189template <typename StreamIdType>
190Http2PriorityWriteScheduler<StreamIdType>::Http2PriorityWriteScheduler() {
191 auto root_stream_info = SpdyMakeUnique<StreamInfo>();
192 root_stream_info_ = root_stream_info.get();
193 root_stream_info->id = kHttp2RootStreamId;
194 root_stream_info->weight = kHttp2DefaultStreamWeight;
195 root_stream_info->parent = nullptr;
196 root_stream_info->priority = 1.0;
197 root_stream_info->ready = false;
198 all_stream_infos_[kHttp2RootStreamId] = std::move(root_stream_info);
199}
200
201template <typename StreamIdType>
fayang89b9af52019-07-23 07:52:49 -0700202bool Http2PriorityWriteScheduler<StreamIdType>::StreamRegistered(
203 StreamIdType stream_id) const {
204 return SpdyContainsKey(all_stream_infos_, stream_id);
205}
206
207template <typename StreamIdType>
208void Http2PriorityWriteScheduler<StreamIdType>::RegisterStream(
209 StreamIdType stream_id,
210 const StreamPrecedenceType& precedence) {
211 // TODO(mpw): uncomment the SPDY_BUG_IF below once all tests
212 // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
213 // appropriate for protocol version under test.
214 //
215 // SPDY_BUG_IF(precedence.is_spdy3_priority())
216 // << "Expected HTTP/2 stream dependency";
217
218 if (StreamRegistered(stream_id)) {
219 SPDY_BUG << "Stream " << stream_id << " already registered";
220 return;
221 }
222
223 StreamInfo* parent = FindStream(precedence.parent_id());
224 if (parent == nullptr) {
225 // parent_id may legitimately not be registered yet--see b/15676312.
226 SPDY_VLOG(1) << "Parent stream " << precedence.parent_id()
227 << " not registered";
228 parent = root_stream_info_;
229 }
230
231 auto new_stream_info = SpdyMakeUnique<StreamInfo>();
232 StreamInfo* new_stream_info_ptr = new_stream_info.get();
233 new_stream_info_ptr->id = stream_id;
234 new_stream_info_ptr->weight = precedence.weight();
235 new_stream_info_ptr->parent = parent;
236 all_stream_infos_[stream_id] = std::move(new_stream_info);
237 if (precedence.is_exclusive()) {
238 // Move the parent's current children below the new stream.
239 using std::swap;
240 swap(new_stream_info_ptr->children, parent->children);
241 new_stream_info_ptr->total_child_weights = parent->total_child_weights;
242 // Update each child's parent.
243 for (StreamInfo* child : new_stream_info_ptr->children) {
244 child->parent = new_stream_info_ptr;
245 }
246 // Clear parent's old child data.
247 DCHECK(parent->children.empty());
248 parent->total_child_weights = 0;
249 }
250 // Add new stream to parent.
251 parent->children.push_back(new_stream_info_ptr);
252 parent->total_child_weights += precedence.weight();
253
254 // Update all priorities under parent, since addition of a stream affects
255 // sibling priorities as well.
256 UpdatePrioritiesUnder(parent);
257
258 // Stream starts with ready == false, so no need to schedule it yet.
259 DCHECK(!new_stream_info_ptr->ready);
260}
261
262template <typename StreamIdType>
263void Http2PriorityWriteScheduler<StreamIdType>::UnregisterStream(
264 StreamIdType stream_id) {
265 if (stream_id == kHttp2RootStreamId) {
266 SPDY_BUG << "Cannot unregister root stream";
267 return;
268 }
269 // Remove the stream from table.
270 auto it = all_stream_infos_.find(stream_id);
271 if (it == all_stream_infos_.end()) {
272 SPDY_BUG << "Stream " << stream_id << " not registered";
273 return;
274 }
275 std::unique_ptr<StreamInfo> stream_info(std::move(it->second));
276 all_stream_infos_.erase(it);
277 // If ready (and hence scheduled), unschedule.
278 if (stream_info->ready) {
279 Unschedule(stream_info.get());
280 }
281
282 StreamInfo* parent = stream_info->parent;
283 // Remove the stream from parent's child list.
284 Remove(&parent->children, stream_info.get());
285 parent->total_child_weights -= stream_info->weight;
286
287 // Move the stream's children to the parent's child list.
288 // Update each child's parent and weight.
289 for (StreamInfo* child : stream_info->children) {
290 child->parent = parent;
291 parent->children.push_back(child);
292 // Divide the removed stream's weight among its children, rounding to the
293 // nearest valid weight.
294 float float_weight = stream_info->weight *
295 static_cast<float>(child->weight) /
296 static_cast<float>(stream_info->total_child_weights);
297 int new_weight = floor(float_weight + 0.5);
298 if (new_weight == 0) {
299 new_weight = 1;
300 }
301 child->weight = new_weight;
302 parent->total_child_weights += child->weight;
303 }
304 UpdatePrioritiesUnder(parent);
305}
306
307template <typename StreamIdType>
308typename Http2PriorityWriteScheduler<StreamIdType>::StreamPrecedenceType
309Http2PriorityWriteScheduler<StreamIdType>::GetStreamPrecedence(
310 StreamIdType stream_id) const {
311 const StreamInfo* stream_info = FindStream(stream_id);
312 if (stream_info == nullptr) {
313 // Unknown streams tolerated due to b/15676312. However, return lowest
314 // weight.
315 SPDY_VLOG(1) << "Stream " << stream_id << " not registered";
316 return StreamPrecedenceType(kHttp2RootStreamId, kHttp2MinStreamWeight,
317 false);
318 }
319 return stream_info->ToStreamPrecedence();
320}
321
322template <typename StreamIdType>
323std::vector<StreamIdType>
324Http2PriorityWriteScheduler<StreamIdType>::GetStreamChildren(
325 StreamIdType stream_id) const {
326 std::vector<StreamIdType> child_vec;
327 const StreamInfo* stream_info = FindStream(stream_id);
328 if (stream_info == nullptr) {
329 SPDY_BUG << "Stream " << stream_id << " not registered";
330 } else {
331 child_vec.reserve(stream_info->children.size());
332 for (StreamInfo* child : stream_info->children) {
333 child_vec.push_back(child->id);
334 }
335 }
336 return child_vec;
337}
338
339template <typename StreamIdType>
340void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamPrecedence(
341 StreamIdType stream_id,
342 const StreamPrecedenceType& precedence) {
343 // TODO(mpw): uncomment the SPDY_BUG_IF below once all tests
344 // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
345 // appropriate for protocol version under test.
346 //
347 // SPDY_BUG_IF(precedence.is_spdy3_priority())
348 // << "Expected HTTP/2 stream dependency";
349 if (stream_id == kHttp2RootStreamId) {
350 SPDY_BUG << "Cannot set precedence of root stream";
351 return;
352 }
353
354 StreamInfo* stream_info = FindStream(stream_id);
355 if (stream_info == nullptr) {
356 // TODO(mpw): add to all_stream_infos_ on demand--see b/15676312.
357 SPDY_VLOG(1) << "Stream " << stream_id << " not registered";
358 return;
359 }
360 UpdateStreamParent(stream_info, precedence.parent_id(),
361 precedence.is_exclusive());
362 UpdateStreamWeight(stream_info, precedence.weight());
363}
364
365template <typename StreamIdType>
366void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamWeight(
367 StreamInfo* stream_info,
368 int weight) {
369 if (weight == stream_info->weight) {
370 return;
371 }
372 if (stream_info->parent != nullptr) {
373 stream_info->parent->total_child_weights += (weight - stream_info->weight);
374 }
375 stream_info->weight = weight;
376
377 // Change in weight also affects sibling priorities.
378 UpdatePrioritiesUnder(stream_info->parent);
379}
380
381template <typename StreamIdType>
382void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamParent(
383 StreamInfo* stream_info,
384 StreamIdType parent_id,
385 bool exclusive) {
386 if (stream_info->id == parent_id) {
387 SPDY_BUG << "Cannot set stream to be its own parent";
388 return;
389 }
390 StreamInfo* new_parent = FindStream(parent_id);
391 if (new_parent == nullptr) {
392 // parent_id may legitimately not be registered yet--see b/15676312.
393 SPDY_VLOG(1) << "Parent stream " << parent_id << " not registered";
394 return;
395 }
396
397 // If the new parent is already the stream's parent, we're done.
398 if (stream_info->parent == new_parent) {
399 return;
400 }
401
402 // Next, check to see if the new parent is currently a descendant
403 // of the stream.
404 StreamInfo* last = new_parent->parent;
405 bool cycle_exists = false;
406 while (last != nullptr) {
407 if (last == stream_info) {
408 cycle_exists = true;
409 break;
410 }
411 last = last->parent;
412 }
413
414 if (cycle_exists) {
415 // The new parent moves to the level of the current stream.
416 UpdateStreamParent(new_parent, stream_info->parent->id, false);
417 }
418
419 // Remove stream from old parent's child list.
420 StreamInfo* old_parent = stream_info->parent;
421 Remove(&old_parent->children, stream_info);
422 old_parent->total_child_weights -= stream_info->weight;
423 UpdatePrioritiesUnder(old_parent);
424
425 if (exclusive) {
426 // Move the new parent's current children below the current stream.
427 for (StreamInfo* child : new_parent->children) {
428 child->parent = stream_info;
429 stream_info->children.push_back(child);
430 }
431 stream_info->total_child_weights += new_parent->total_child_weights;
432 // Clear new parent's old child data.
433 new_parent->children.clear();
434 new_parent->total_child_weights = 0;
435 }
436
437 // Make the change.
438 stream_info->parent = new_parent;
439 new_parent->children.push_back(stream_info);
440 new_parent->total_child_weights += stream_info->weight;
441 UpdatePrioritiesUnder(new_parent);
442}
443
444template <typename StreamIdType>
445void Http2PriorityWriteScheduler<StreamIdType>::RecordStreamEventTime(
446 StreamIdType stream_id,
447 int64_t now_in_usec) {
448 if (stream_id == kHttp2RootStreamId) {
449 SPDY_BUG << "Cannot record event time for root stream";
450 return;
451 }
452 StreamInfo* stream_info = FindStream(stream_id);
453 if (stream_info == nullptr) {
454 SPDY_BUG << "Stream " << stream_id << " not registered";
455 return;
456 }
457 stream_info->last_event_time_usec = now_in_usec;
458}
459
460// O(n) in the number of streams, which isn't great. However, this method will
461// soon be superseded by
462// Http2WeightedWriteScheduler::GetLatestEventWithPrecedence(), for which an
463// efficient implementation is straightforward. Also, this method is only
464// called when calculating idle timeouts, so performance isn't key.
465template <typename StreamIdType>
466int64_t Http2PriorityWriteScheduler<StreamIdType>::GetLatestEventWithPrecedence(
467 StreamIdType stream_id) const {
468 if (stream_id == kHttp2RootStreamId) {
469 SPDY_BUG << "Invalid argument: root stream";
470 return 0;
471 }
472 const StreamInfo* stream_info = FindStream(stream_id);
473 if (stream_info == nullptr) {
474 SPDY_BUG << "Stream " << stream_id << " not registered";
475 return 0;
476 }
477 int64_t last_event_time_usec = 0;
478 for (const auto& kv : all_stream_infos_) {
479 const StreamInfo& other = *kv.second;
480 if (other.priority > stream_info->priority) {
481 last_event_time_usec =
482 std::max(last_event_time_usec, other.last_event_time_usec);
483 }
484 }
485 return last_event_time_usec;
486}
487
488// Worst-case time complexity of O(n*d), where n is scheduling queue length and
489// d is tree depth. In practice, should be much shorter, since loop terminates
490// at first writable stream or |stream_id| (whichever is first).
491template <typename StreamIdType>
492bool Http2PriorityWriteScheduler<StreamIdType>::ShouldYield(
493 StreamIdType stream_id) const {
494 if (stream_id == kHttp2RootStreamId) {
495 SPDY_BUG << "Invalid argument: root stream";
496 return false;
497 }
498 const StreamInfo* stream_info = FindStream(stream_id);
499 if (stream_info == nullptr) {
500 SPDY_BUG << "Stream " << stream_id << " not registered";
501 return false;
502 }
503 for (const StreamInfo& scheduled : scheduling_queue_) {
504 if (stream_info == &scheduled) {
505 return false;
506 }
507 if (!HasReadyAncestor(scheduled)) {
508 return true;
509 }
510 }
511 return false;
512}
513
514template <typename StreamIdType>
515void Http2PriorityWriteScheduler<StreamIdType>::MarkStreamReady(
516 StreamIdType stream_id,
517 bool add_to_front) {
518 if (stream_id == kHttp2RootStreamId) {
519 SPDY_BUG << "Cannot mark root stream ready";
520 return;
521 }
522 StreamInfo* stream_info = FindStream(stream_id);
523 if (stream_info == nullptr) {
524 SPDY_BUG << "Stream " << stream_id << " not registered";
525 return;
526 }
527 if (stream_info->ready) {
528 return;
529 }
530 stream_info->ordinal = add_to_front ? head_ordinal_-- : tail_ordinal_++;
531 Schedule(stream_info);
532}
533
534template <typename StreamIdType>
535void Http2PriorityWriteScheduler<StreamIdType>::MarkStreamNotReady(
536 StreamIdType stream_id) {
537 if (stream_id == kHttp2RootStreamId) {
538 SPDY_BUG << "Cannot mark root stream unready";
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 Unschedule(stream_info);
550}
551
552template <typename StreamIdType>
553bool Http2PriorityWriteScheduler<StreamIdType>::Remove(
554 StreamInfoVector* stream_infos,
555 const StreamInfo* stream_info) {
556 for (typename StreamInfoVector::iterator it = stream_infos->begin();
557 it != stream_infos->end(); ++it) {
558 if (*it == stream_info) {
559 stream_infos->erase(it);
560 return true;
561 }
562 }
563 return false;
564}
565
566template <typename StreamIdType>
567bool Http2PriorityWriteScheduler<StreamIdType>::HasReadyAncestor(
568 const StreamInfo& stream_info) {
569 for (const StreamInfo* parent = stream_info.parent; parent != nullptr;
570 parent = parent->parent) {
571 if (parent->ready) {
572 return true;
573 }
574 }
575 return false;
576}
577
578template <typename StreamIdType>
579const typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
580Http2PriorityWriteScheduler<StreamIdType>::FindStream(
581 StreamIdType stream_id) const {
582 auto it = all_stream_infos_.find(stream_id);
583 return it == all_stream_infos_.end() ? nullptr : it->second.get();
584}
585
586template <typename StreamIdType>
587typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
588Http2PriorityWriteScheduler<StreamIdType>::FindStream(StreamIdType stream_id) {
589 auto it = all_stream_infos_.find(stream_id);
590 return it == all_stream_infos_.end() ? nullptr : it->second.get();
591}
592
593template <typename StreamIdType>
594void Http2PriorityWriteScheduler<StreamIdType>::UpdatePrioritiesUnder(
595 StreamInfo* stream_info) {
596 for (StreamInfo* child : stream_info->children) {
597 child->priority = stream_info->priority *
598 (static_cast<float>(child->weight) /
599 static_cast<float>(stream_info->total_child_weights));
600 if (child->ready) {
601 // Reposition in scheduling_queue_. Use post-order for scheduling, to
602 // benefit from the fact that children have priority <= parent priority.
603 Unschedule(child);
604 UpdatePrioritiesUnder(child);
605 Schedule(child);
606 } else {
607 UpdatePrioritiesUnder(child);
608 }
609 }
610}
611
612template <typename StreamIdType>
613void Http2PriorityWriteScheduler<StreamIdType>::Schedule(
614 StreamInfo* stream_info) {
615 DCHECK(!stream_info->ready);
616 for (StreamInfo& s : scheduling_queue_) {
617 if (stream_info->SchedulesBefore(s)) {
618 scheduling_queue_.insert(&s, stream_info);
619 stream_info->ready = true;
620 return;
621 }
622 }
623 scheduling_queue_.push_back(stream_info);
624 stream_info->ready = true;
625}
626
627template <typename StreamIdType>
628void Http2PriorityWriteScheduler<StreamIdType>::Unschedule(
629 StreamInfo* stream_info) {
630 DCHECK(stream_info->ready);
631 scheduling_queue_.erase(stream_info);
632 stream_info->ready = false;
633}
634
635template <typename StreamIdType>
636bool Http2PriorityWriteScheduler<StreamIdType>::StreamHasChild(
637 const StreamInfo& parent_info,
638 const StreamInfo* child_info) const {
639 auto found = std::find(parent_info.children.begin(),
640 parent_info.children.end(), child_info);
641 return found != parent_info.children.end();
642}
643
644template <typename StreamIdType>
645bool Http2PriorityWriteScheduler<StreamIdType>::HasReadyStreams() const {
646 return !scheduling_queue_.empty();
647}
648
649template <typename StreamIdType>
650StreamIdType Http2PriorityWriteScheduler<StreamIdType>::PopNextReadyStream() {
651 return std::get<0>(PopNextReadyStreamAndPrecedence());
652}
653
654template <typename StreamIdType>
655std::tuple<
656 StreamIdType,
657 typename Http2PriorityWriteScheduler<StreamIdType>::StreamPrecedenceType>
658Http2PriorityWriteScheduler<StreamIdType>::PopNextReadyStreamAndPrecedence() {
659 for (StreamInfo& stream_info : scheduling_queue_) {
660 if (!HasReadyAncestor(stream_info)) {
661 Unschedule(&stream_info);
662 return std::make_tuple(stream_info.id, stream_info.ToStreamPrecedence());
663 }
664 }
665 SPDY_BUG << "No ready streams";
666 return std::make_tuple(
667 kHttp2RootStreamId,
668 StreamPrecedenceType(kHttp2RootStreamId, kHttp2MinStreamWeight, false));
669}
670
671template <typename StreamIdType>
672size_t Http2PriorityWriteScheduler<StreamIdType>::NumReadyStreams() const {
673 return scheduling_queue_.size();
674}
675
676template <typename StreamIdType>
677bool Http2PriorityWriteScheduler<StreamIdType>::IsStreamReady(
678 StreamIdType stream_id) const {
679 if (stream_id == kHttp2RootStreamId) {
680 SPDY_BUG << "Try to check whether root stream is ready";
681 return false;
682 }
683 const StreamInfo* stream_info = FindStream(stream_id);
684 if (stream_info == nullptr) {
685 SPDY_BUG << "Stream " << stream_id << " not registered";
686 return false;
687 }
688 return stream_info->ready;
689}
690
691template <typename StreamIdType>
fayang753002d2019-07-24 11:54:43 -0700692size_t Http2PriorityWriteScheduler<StreamIdType>::NumRegisteredStreams() const {
693 return all_stream_infos_.size();
694}
695
696template <typename StreamIdType>
fayang89b9af52019-07-23 07:52:49 -0700697SpdyString Http2PriorityWriteScheduler<StreamIdType>::DebugString() const {
fayang753002d2019-07-24 11:54:43 -0700698 return SpdyStrCat("Http2PriorityWriteScheduler {num_registered_streams=",
699 NumRegisteredStreams(),
fayang89b9af52019-07-23 07:52:49 -0700700 " num_ready_streams=", NumReadyStreams(), "}");
701}
702
703template <typename StreamIdType>
704bool Http2PriorityWriteScheduler<StreamIdType>::ValidateInvariantsForTests()
705 const {
706 int total_streams = 0;
707 int streams_visited = 0;
708 // Iterate through all streams in the map.
709 for (const auto& kv : all_stream_infos_) {
710 ++total_streams;
711 ++streams_visited;
712 StreamIdType stream_id = kv.first;
713 const StreamInfo& stream_info = *kv.second;
714
715 // Verify each StreamInfo mapped under the proper stream ID.
716 if (stream_id != stream_info.id) {
717 SPDY_DLOG(INFO) << "Stream ID " << stream_id
718 << " maps to StreamInfo with ID " << stream_info.id;
719 return false;
720 }
721
722 // All streams except the root should have a parent, and should appear in
723 // the children of that parent.
724 if (stream_info.id != kHttp2RootStreamId &&
725 !StreamHasChild(*stream_info.parent, &stream_info)) {
726 SPDY_DLOG(INFO) << "Parent stream " << stream_info.parent->id
727 << " is not registered, or does not list stream "
728 << stream_info.id << " as its child.";
729 return false;
730 }
731
732 if (!stream_info.children.empty()) {
733 int total_child_weights = 0;
734 // Iterate through the stream's children.
735 for (StreamInfo* child : stream_info.children) {
736 ++streams_visited;
737 // Each stream in the list should exist and should have this stream
738 // set as its parent.
739 if (!StreamRegistered(child->id) || child->parent != &stream_info) {
740 SPDY_DLOG(INFO) << "Child stream " << child->id
741 << " is not registered, "
742 << "or does not list " << stream_info.id
743 << " as its parent.";
744 return false;
745 }
746 total_child_weights += child->weight;
747 }
748 // Verify that total_child_weights is correct.
749 if (total_child_weights != stream_info.total_child_weights) {
750 SPDY_DLOG(INFO) << "Child weight totals do not agree. For stream "
751 << stream_info.id << " total_child_weights has value "
752 << stream_info.total_child_weights << ", expected "
753 << total_child_weights;
754 return false;
755 }
756 }
757 }
758
fayang753002d2019-07-24 11:54:43 -0700759 // Make sure NumRegisteredStreams() reflects the total number of streams the
760 // map contains.
761 if (total_streams != NumRegisteredStreams()) {
fayang89b9af52019-07-23 07:52:49 -0700762 SPDY_DLOG(INFO) << "Map contains incorrect number of streams.";
763 return false;
764 }
765 // Validate the validation function; we should have visited each stream twice
766 // (except for the root)
fayang753002d2019-07-24 11:54:43 -0700767 DCHECK(streams_visited == 2 * NumRegisteredStreams() - 1);
fayang89b9af52019-07-23 07:52:49 -0700768 return true;
769}
770
771} // namespace spdy
772
773#endif // QUICHE_SPDY_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_