gfe-relnote: Move Http2PriorityWriteScheduler from gfe/gfe2/spdy/ to third_party/spdy/core/ to allow QUIC to use it. No functional change expected, not flag protected.
Also copy gtl::intrusive_list as SpdyIntrusiveList.
PiperOrigin-RevId: 259535206
Change-Id: Ie600cc4a403ac90a6302824f98bc5e7fd86fca25
diff --git a/spdy/core/http2_priority_write_scheduler.h b/spdy/core/http2_priority_write_scheduler.h
new file mode 100644
index 0000000..1f72fdb
--- /dev/null
+++ b/spdy/core/http2_priority_write_scheduler.h
@@ -0,0 +1,774 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_
+#define QUICHE_SPDY_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_
+
+#include <cstdint>
+#include <deque>
+#include <map>
+#include <memory>
+#include <queue>
+#include <set>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+#include "net/third_party/quiche/src/spdy/core/write_scheduler.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_bug_tracker.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_containers.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_logging.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_map_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_ptr_util.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string_utils.h"
+
+namespace spdy {
+
+namespace test {
+template <typename StreamIdType>
+class Http2PriorityWriteSchedulerPeer;
+}
+
+// This data structure implements the HTTP/2 stream priority tree defined in
+// section 5.3 of RFC 7540:
+// http://tools.ietf.org/html/rfc7540#section-5.3
+//
+// Streams can be added and removed, and dependencies between them defined.
+// Streams constitute a tree rooted at stream ID 0: each stream has a single
+// parent stream, and 0 or more child streams. Individual streams can be
+// marked as ready to read/write, and then the whole structure can be queried
+// to pick the next stream to read/write out of those that are ready.
+template <typename StreamIdType>
+class Http2PriorityWriteScheduler : public WriteScheduler<StreamIdType> {
+ public:
+ using typename WriteScheduler<StreamIdType>::StreamPrecedenceType;
+
+ Http2PriorityWriteScheduler();
+ Http2PriorityWriteScheduler(const Http2PriorityWriteScheduler&) = delete;
+ Http2PriorityWriteScheduler& operator=(const Http2PriorityWriteScheduler&) =
+ delete;
+
+ // WriteScheduler methods
+ void RegisterStream(StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) override;
+ void UnregisterStream(StreamIdType stream_id) override;
+ bool StreamRegistered(StreamIdType stream_id) const override;
+ StreamPrecedenceType GetStreamPrecedence(
+ StreamIdType stream_id) const override;
+ void UpdateStreamPrecedence(StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) override;
+ std::vector<StreamIdType> GetStreamChildren(
+ StreamIdType stream_id) const override;
+ void RecordStreamEventTime(StreamIdType stream_id,
+ int64_t now_in_usec) override;
+ int64_t GetLatestEventWithPrecedence(StreamIdType stream_id) const override;
+ bool ShouldYield(StreamIdType stream_id) const override;
+ void MarkStreamReady(StreamIdType stream_id, bool add_to_front) override;
+ void MarkStreamNotReady(StreamIdType stream_id) override;
+ bool HasReadyStreams() const override;
+ StreamIdType PopNextReadyStream() override;
+ std::tuple<StreamIdType, StreamPrecedenceType>
+ PopNextReadyStreamAndPrecedence() override;
+ size_t NumReadyStreams() const override;
+ bool IsStreamReady(StreamIdType stream_id) const override;
+ SpdyString DebugString() const override;
+
+ // Return the number of streams currently in the tree.
+ int num_streams() const;
+
+ private:
+ friend class test::Http2PriorityWriteSchedulerPeer<StreamIdType>;
+
+ struct StreamInfo;
+ typedef SpdyInlinedVector<StreamInfo*, 4> StreamInfoVector;
+
+ struct StreamInfo : public SpdyIntrusiveLink<StreamInfo> {
+ // ID for this stream.
+ StreamIdType id;
+ // StreamInfo for parent stream.
+ StreamInfo* parent = nullptr;
+ // Weights can range between 1 and 256 (inclusive).
+ int weight = kHttp2DefaultStreamWeight;
+ // The total weight of this stream's direct descendants.
+ int total_child_weights = 0;
+ // Pointers to StreamInfos for children, if any.
+ StreamInfoVector children;
+ // Whether the stream is ready for writing. The stream is present in
+ // scheduling_queue_ iff true.
+ bool ready = false;
+ // The scheduling priority of this stream. Streams with higher priority
+ // values are scheduled first.
+ // TODO(mpw): rename to avoid confusion with SPDY priorities,
+ // which this is not.
+ float priority = 0;
+ // Ordinal value for this stream, used to ensure round-robin scheduling:
+ // among streams with the same scheduling priority, streams with lower
+ // ordinal are scheduled first.
+ int64_t ordinal = 0;
+ // Time of latest write event for stream of this priority, in microseconds.
+ int64_t last_event_time_usec = 0;
+
+ // Whether this stream should be scheduled ahead of another stream.
+ bool SchedulesBefore(const StreamInfo& other) const {
+ return (priority != other.priority) ? priority > other.priority
+ : ordinal < other.ordinal;
+ }
+
+ // Returns the StreamPrecedenceType for this StreamInfo.
+ StreamPrecedenceType ToStreamPrecedence() const {
+ StreamIdType parent_id =
+ parent == nullptr ? kHttp2RootStreamId : parent->id;
+ bool exclusive = parent != nullptr && parent->children.size() == 1;
+ return StreamPrecedenceType(parent_id, weight, exclusive);
+ }
+ };
+
+ static bool Remove(StreamInfoVector* stream_infos,
+ const StreamInfo* stream_info);
+
+ // Returns true iff any direct or transitive parent of the given stream is
+ // currently ready.
+ static bool HasReadyAncestor(const StreamInfo& stream_info);
+
+ // Returns StreamInfo for the given stream, or nullptr if it isn't
+ // registered.
+ const StreamInfo* FindStream(StreamIdType stream_id) const;
+ StreamInfo* FindStream(StreamIdType stream_id);
+
+ // Helpers for UpdateStreamPrecedence().
+ void UpdateStreamParent(StreamInfo* stream_info,
+ StreamIdType parent_id,
+ bool exclusive);
+ void UpdateStreamWeight(StreamInfo* stream_info, int weight);
+
+ // Update all priority values in the subtree rooted at the given stream, not
+ // including the stream itself. If this results in priority value changes for
+ // scheduled streams, those streams are rescheduled to ensure proper ordering
+ // of scheduling_queue_.
+ // TODO(mpw): rename to avoid confusion with SPDY priorities.
+ void UpdatePrioritiesUnder(StreamInfo* stream_info);
+
+ // Inserts stream into scheduling_queue_ at the appropriate location given
+ // its priority and ordinal. Time complexity is O(scheduling_queue.size()).
+ void Schedule(StreamInfo* stream_info);
+
+ // Removes stream from scheduling_queue_.
+ void Unschedule(StreamInfo* stream_info);
+
+ // Return true if all internal invariants hold (useful for unit tests).
+ // Unless there are bugs, this should always return true.
+ bool ValidateInvariantsForTests() const;
+
+ // Returns true if the parent stream has the given stream in its children.
+ bool StreamHasChild(const StreamInfo& parent_info,
+ const StreamInfo* child_info) const;
+
+ // Pointee owned by all_stream_infos_.
+ StreamInfo* root_stream_info_;
+ // Maps from stream IDs to StreamInfo objects.
+ SpdySmallMap<StreamIdType, std::unique_ptr<StreamInfo>, 10> all_stream_infos_;
+ // Queue containing all ready streams, ordered with streams of higher
+ // priority before streams of lower priority, and, among streams of equal
+ // priority, streams with lower ordinal before those with higher
+ // ordinal. Note that not all streams in scheduling_queue_ are eligible to be
+ // picked as the next stream: some may have ancestor stream(s) that are ready
+ // and unblocked. In these situations the occluded child streams are left in
+ // the queue, to reduce churn.
+ SpdyIntrusiveList<StreamInfo> scheduling_queue_;
+ // Ordinal value to assign to next node inserted into scheduling_queue_ when
+ // |add_to_front == true|. Decremented after each assignment.
+ int64_t head_ordinal_ = -1;
+ // Ordinal value to assign to next node inserted into scheduling_queue_ when
+ // |add_to_front == false|. Incremented after each assignment.
+ int64_t tail_ordinal_ = 0;
+};
+
+template <typename StreamIdType>
+Http2PriorityWriteScheduler<StreamIdType>::Http2PriorityWriteScheduler() {
+ auto root_stream_info = SpdyMakeUnique<StreamInfo>();
+ root_stream_info_ = root_stream_info.get();
+ root_stream_info->id = kHttp2RootStreamId;
+ root_stream_info->weight = kHttp2DefaultStreamWeight;
+ root_stream_info->parent = nullptr;
+ root_stream_info->priority = 1.0;
+ root_stream_info->ready = false;
+ all_stream_infos_[kHttp2RootStreamId] = std::move(root_stream_info);
+}
+
+template <typename StreamIdType>
+int Http2PriorityWriteScheduler<StreamIdType>::num_streams() const {
+ return all_stream_infos_.size();
+}
+
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::StreamRegistered(
+ StreamIdType stream_id) const {
+ return SpdyContainsKey(all_stream_infos_, stream_id);
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::RegisterStream(
+ StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) {
+ // TODO(mpw): uncomment the SPDY_BUG_IF below once all tests
+ // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
+ // appropriate for protocol version under test.
+ //
+ // SPDY_BUG_IF(precedence.is_spdy3_priority())
+ // << "Expected HTTP/2 stream dependency";
+
+ if (StreamRegistered(stream_id)) {
+ SPDY_BUG << "Stream " << stream_id << " already registered";
+ return;
+ }
+
+ StreamInfo* parent = FindStream(precedence.parent_id());
+ if (parent == nullptr) {
+ // parent_id may legitimately not be registered yet--see b/15676312.
+ SPDY_VLOG(1) << "Parent stream " << precedence.parent_id()
+ << " not registered";
+ parent = root_stream_info_;
+ }
+
+ auto new_stream_info = SpdyMakeUnique<StreamInfo>();
+ StreamInfo* new_stream_info_ptr = new_stream_info.get();
+ new_stream_info_ptr->id = stream_id;
+ new_stream_info_ptr->weight = precedence.weight();
+ new_stream_info_ptr->parent = parent;
+ all_stream_infos_[stream_id] = std::move(new_stream_info);
+ if (precedence.is_exclusive()) {
+ // Move the parent's current children below the new stream.
+ using std::swap;
+ swap(new_stream_info_ptr->children, parent->children);
+ new_stream_info_ptr->total_child_weights = parent->total_child_weights;
+ // Update each child's parent.
+ for (StreamInfo* child : new_stream_info_ptr->children) {
+ child->parent = new_stream_info_ptr;
+ }
+ // Clear parent's old child data.
+ DCHECK(parent->children.empty());
+ parent->total_child_weights = 0;
+ }
+ // Add new stream to parent.
+ parent->children.push_back(new_stream_info_ptr);
+ parent->total_child_weights += precedence.weight();
+
+ // Update all priorities under parent, since addition of a stream affects
+ // sibling priorities as well.
+ UpdatePrioritiesUnder(parent);
+
+ // Stream starts with ready == false, so no need to schedule it yet.
+ DCHECK(!new_stream_info_ptr->ready);
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::UnregisterStream(
+ StreamIdType stream_id) {
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Cannot unregister root stream";
+ return;
+ }
+ // Remove the stream from table.
+ auto it = all_stream_infos_.find(stream_id);
+ if (it == all_stream_infos_.end()) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ std::unique_ptr<StreamInfo> stream_info(std::move(it->second));
+ all_stream_infos_.erase(it);
+ // If ready (and hence scheduled), unschedule.
+ if (stream_info->ready) {
+ Unschedule(stream_info.get());
+ }
+
+ StreamInfo* parent = stream_info->parent;
+ // Remove the stream from parent's child list.
+ Remove(&parent->children, stream_info.get());
+ parent->total_child_weights -= stream_info->weight;
+
+ // Move the stream's children to the parent's child list.
+ // Update each child's parent and weight.
+ for (StreamInfo* child : stream_info->children) {
+ child->parent = parent;
+ parent->children.push_back(child);
+ // Divide the removed stream's weight among its children, rounding to the
+ // nearest valid weight.
+ float float_weight = stream_info->weight *
+ static_cast<float>(child->weight) /
+ static_cast<float>(stream_info->total_child_weights);
+ int new_weight = floor(float_weight + 0.5);
+ if (new_weight == 0) {
+ new_weight = 1;
+ }
+ child->weight = new_weight;
+ parent->total_child_weights += child->weight;
+ }
+ UpdatePrioritiesUnder(parent);
+}
+
+template <typename StreamIdType>
+typename Http2PriorityWriteScheduler<StreamIdType>::StreamPrecedenceType
+Http2PriorityWriteScheduler<StreamIdType>::GetStreamPrecedence(
+ StreamIdType stream_id) const {
+ const StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ // Unknown streams tolerated due to b/15676312. However, return lowest
+ // weight.
+ SPDY_VLOG(1) << "Stream " << stream_id << " not registered";
+ return StreamPrecedenceType(kHttp2RootStreamId, kHttp2MinStreamWeight,
+ false);
+ }
+ return stream_info->ToStreamPrecedence();
+}
+
+template <typename StreamIdType>
+std::vector<StreamIdType>
+Http2PriorityWriteScheduler<StreamIdType>::GetStreamChildren(
+ StreamIdType stream_id) const {
+ std::vector<StreamIdType> child_vec;
+ const StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ } else {
+ child_vec.reserve(stream_info->children.size());
+ for (StreamInfo* child : stream_info->children) {
+ child_vec.push_back(child->id);
+ }
+ }
+ return child_vec;
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamPrecedence(
+ StreamIdType stream_id,
+ const StreamPrecedenceType& precedence) {
+ // TODO(mpw): uncomment the SPDY_BUG_IF below once all tests
+ // (e.g. SpdyClientDispatcher) modified to pass StreamPrecedence instances
+ // appropriate for protocol version under test.
+ //
+ // SPDY_BUG_IF(precedence.is_spdy3_priority())
+ // << "Expected HTTP/2 stream dependency";
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Cannot set precedence of root stream";
+ return;
+ }
+
+ StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ // TODO(mpw): add to all_stream_infos_ on demand--see b/15676312.
+ SPDY_VLOG(1) << "Stream " << stream_id << " not registered";
+ return;
+ }
+ UpdateStreamParent(stream_info, precedence.parent_id(),
+ precedence.is_exclusive());
+ UpdateStreamWeight(stream_info, precedence.weight());
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamWeight(
+ StreamInfo* stream_info,
+ int weight) {
+ if (weight == stream_info->weight) {
+ return;
+ }
+ if (stream_info->parent != nullptr) {
+ stream_info->parent->total_child_weights += (weight - stream_info->weight);
+ }
+ stream_info->weight = weight;
+
+ // Change in weight also affects sibling priorities.
+ UpdatePrioritiesUnder(stream_info->parent);
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::UpdateStreamParent(
+ StreamInfo* stream_info,
+ StreamIdType parent_id,
+ bool exclusive) {
+ if (stream_info->id == parent_id) {
+ SPDY_BUG << "Cannot set stream to be its own parent";
+ return;
+ }
+ StreamInfo* new_parent = FindStream(parent_id);
+ if (new_parent == nullptr) {
+ // parent_id may legitimately not be registered yet--see b/15676312.
+ SPDY_VLOG(1) << "Parent stream " << parent_id << " not registered";
+ return;
+ }
+
+ // If the new parent is already the stream's parent, we're done.
+ if (stream_info->parent == new_parent) {
+ return;
+ }
+
+ // Next, check to see if the new parent is currently a descendant
+ // of the stream.
+ StreamInfo* last = new_parent->parent;
+ bool cycle_exists = false;
+ while (last != nullptr) {
+ if (last == stream_info) {
+ cycle_exists = true;
+ break;
+ }
+ last = last->parent;
+ }
+
+ if (cycle_exists) {
+ // The new parent moves to the level of the current stream.
+ UpdateStreamParent(new_parent, stream_info->parent->id, false);
+ }
+
+ // Remove stream from old parent's child list.
+ StreamInfo* old_parent = stream_info->parent;
+ Remove(&old_parent->children, stream_info);
+ old_parent->total_child_weights -= stream_info->weight;
+ UpdatePrioritiesUnder(old_parent);
+
+ if (exclusive) {
+ // Move the new parent's current children below the current stream.
+ for (StreamInfo* child : new_parent->children) {
+ child->parent = stream_info;
+ stream_info->children.push_back(child);
+ }
+ stream_info->total_child_weights += new_parent->total_child_weights;
+ // Clear new parent's old child data.
+ new_parent->children.clear();
+ new_parent->total_child_weights = 0;
+ }
+
+ // Make the change.
+ stream_info->parent = new_parent;
+ new_parent->children.push_back(stream_info);
+ new_parent->total_child_weights += stream_info->weight;
+ UpdatePrioritiesUnder(new_parent);
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::RecordStreamEventTime(
+ StreamIdType stream_id,
+ int64_t now_in_usec) {
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Cannot record event time for root stream";
+ return;
+ }
+ StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ stream_info->last_event_time_usec = now_in_usec;
+}
+
+// O(n) in the number of streams, which isn't great. However, this method will
+// soon be superseded by
+// Http2WeightedWriteScheduler::GetLatestEventWithPrecedence(), for which an
+// efficient implementation is straightforward. Also, this method is only
+// called when calculating idle timeouts, so performance isn't key.
+template <typename StreamIdType>
+int64_t Http2PriorityWriteScheduler<StreamIdType>::GetLatestEventWithPrecedence(
+ StreamIdType stream_id) const {
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Invalid argument: root stream";
+ return 0;
+ }
+ const StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return 0;
+ }
+ int64_t last_event_time_usec = 0;
+ for (const auto& kv : all_stream_infos_) {
+ const StreamInfo& other = *kv.second;
+ if (other.priority > stream_info->priority) {
+ last_event_time_usec =
+ std::max(last_event_time_usec, other.last_event_time_usec);
+ }
+ }
+ return last_event_time_usec;
+}
+
+// Worst-case time complexity of O(n*d), where n is scheduling queue length and
+// d is tree depth. In practice, should be much shorter, since loop terminates
+// at first writable stream or |stream_id| (whichever is first).
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::ShouldYield(
+ StreamIdType stream_id) const {
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Invalid argument: root stream";
+ return false;
+ }
+ const StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return false;
+ }
+ for (const StreamInfo& scheduled : scheduling_queue_) {
+ if (stream_info == &scheduled) {
+ return false;
+ }
+ if (!HasReadyAncestor(scheduled)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::MarkStreamReady(
+ StreamIdType stream_id,
+ bool add_to_front) {
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Cannot mark root stream ready";
+ return;
+ }
+ StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ if (stream_info->ready) {
+ return;
+ }
+ stream_info->ordinal = add_to_front ? head_ordinal_-- : tail_ordinal_++;
+ Schedule(stream_info);
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::MarkStreamNotReady(
+ StreamIdType stream_id) {
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Cannot mark root stream unready";
+ return;
+ }
+ StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return;
+ }
+ if (!stream_info->ready) {
+ return;
+ }
+ Unschedule(stream_info);
+}
+
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::Remove(
+ StreamInfoVector* stream_infos,
+ const StreamInfo* stream_info) {
+ for (typename StreamInfoVector::iterator it = stream_infos->begin();
+ it != stream_infos->end(); ++it) {
+ if (*it == stream_info) {
+ stream_infos->erase(it);
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::HasReadyAncestor(
+ const StreamInfo& stream_info) {
+ for (const StreamInfo* parent = stream_info.parent; parent != nullptr;
+ parent = parent->parent) {
+ if (parent->ready) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename StreamIdType>
+const typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
+Http2PriorityWriteScheduler<StreamIdType>::FindStream(
+ StreamIdType stream_id) const {
+ auto it = all_stream_infos_.find(stream_id);
+ return it == all_stream_infos_.end() ? nullptr : it->second.get();
+}
+
+template <typename StreamIdType>
+typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
+Http2PriorityWriteScheduler<StreamIdType>::FindStream(StreamIdType stream_id) {
+ auto it = all_stream_infos_.find(stream_id);
+ return it == all_stream_infos_.end() ? nullptr : it->second.get();
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::UpdatePrioritiesUnder(
+ StreamInfo* stream_info) {
+ for (StreamInfo* child : stream_info->children) {
+ child->priority = stream_info->priority *
+ (static_cast<float>(child->weight) /
+ static_cast<float>(stream_info->total_child_weights));
+ if (child->ready) {
+ // Reposition in scheduling_queue_. Use post-order for scheduling, to
+ // benefit from the fact that children have priority <= parent priority.
+ Unschedule(child);
+ UpdatePrioritiesUnder(child);
+ Schedule(child);
+ } else {
+ UpdatePrioritiesUnder(child);
+ }
+ }
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::Schedule(
+ StreamInfo* stream_info) {
+ DCHECK(!stream_info->ready);
+ for (StreamInfo& s : scheduling_queue_) {
+ if (stream_info->SchedulesBefore(s)) {
+ scheduling_queue_.insert(&s, stream_info);
+ stream_info->ready = true;
+ return;
+ }
+ }
+ scheduling_queue_.push_back(stream_info);
+ stream_info->ready = true;
+}
+
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::Unschedule(
+ StreamInfo* stream_info) {
+ DCHECK(stream_info->ready);
+ scheduling_queue_.erase(stream_info);
+ stream_info->ready = false;
+}
+
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::StreamHasChild(
+ const StreamInfo& parent_info,
+ const StreamInfo* child_info) const {
+ auto found = std::find(parent_info.children.begin(),
+ parent_info.children.end(), child_info);
+ return found != parent_info.children.end();
+}
+
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::HasReadyStreams() const {
+ return !scheduling_queue_.empty();
+}
+
+template <typename StreamIdType>
+StreamIdType Http2PriorityWriteScheduler<StreamIdType>::PopNextReadyStream() {
+ return std::get<0>(PopNextReadyStreamAndPrecedence());
+}
+
+template <typename StreamIdType>
+std::tuple<
+ StreamIdType,
+ typename Http2PriorityWriteScheduler<StreamIdType>::StreamPrecedenceType>
+Http2PriorityWriteScheduler<StreamIdType>::PopNextReadyStreamAndPrecedence() {
+ for (StreamInfo& stream_info : scheduling_queue_) {
+ if (!HasReadyAncestor(stream_info)) {
+ Unschedule(&stream_info);
+ return std::make_tuple(stream_info.id, stream_info.ToStreamPrecedence());
+ }
+ }
+ SPDY_BUG << "No ready streams";
+ return std::make_tuple(
+ kHttp2RootStreamId,
+ StreamPrecedenceType(kHttp2RootStreamId, kHttp2MinStreamWeight, false));
+}
+
+template <typename StreamIdType>
+size_t Http2PriorityWriteScheduler<StreamIdType>::NumReadyStreams() const {
+ return scheduling_queue_.size();
+}
+
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::IsStreamReady(
+ StreamIdType stream_id) const {
+ if (stream_id == kHttp2RootStreamId) {
+ SPDY_BUG << "Try to check whether root stream is ready";
+ return false;
+ }
+ const StreamInfo* stream_info = FindStream(stream_id);
+ if (stream_info == nullptr) {
+ SPDY_BUG << "Stream " << stream_id << " not registered";
+ return false;
+ }
+ return stream_info->ready;
+}
+
+template <typename StreamIdType>
+SpdyString Http2PriorityWriteScheduler<StreamIdType>::DebugString() const {
+ return SpdyStrCat("Http2PriorityWriteScheduler {num_streams=", num_streams(),
+ " num_ready_streams=", NumReadyStreams(), "}");
+}
+
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::ValidateInvariantsForTests()
+ const {
+ int total_streams = 0;
+ int streams_visited = 0;
+ // Iterate through all streams in the map.
+ for (const auto& kv : all_stream_infos_) {
+ ++total_streams;
+ ++streams_visited;
+ StreamIdType stream_id = kv.first;
+ const StreamInfo& stream_info = *kv.second;
+
+ // Verify each StreamInfo mapped under the proper stream ID.
+ if (stream_id != stream_info.id) {
+ SPDY_DLOG(INFO) << "Stream ID " << stream_id
+ << " maps to StreamInfo with ID " << stream_info.id;
+ return false;
+ }
+
+ // All streams except the root should have a parent, and should appear in
+ // the children of that parent.
+ if (stream_info.id != kHttp2RootStreamId &&
+ !StreamHasChild(*stream_info.parent, &stream_info)) {
+ SPDY_DLOG(INFO) << "Parent stream " << stream_info.parent->id
+ << " is not registered, or does not list stream "
+ << stream_info.id << " as its child.";
+ return false;
+ }
+
+ if (!stream_info.children.empty()) {
+ int total_child_weights = 0;
+ // Iterate through the stream's children.
+ for (StreamInfo* child : stream_info.children) {
+ ++streams_visited;
+ // Each stream in the list should exist and should have this stream
+ // set as its parent.
+ if (!StreamRegistered(child->id) || child->parent != &stream_info) {
+ SPDY_DLOG(INFO) << "Child stream " << child->id
+ << " is not registered, "
+ << "or does not list " << stream_info.id
+ << " as its parent.";
+ return false;
+ }
+ total_child_weights += child->weight;
+ }
+ // Verify that total_child_weights is correct.
+ if (total_child_weights != stream_info.total_child_weights) {
+ SPDY_DLOG(INFO) << "Child weight totals do not agree. For stream "
+ << stream_info.id << " total_child_weights has value "
+ << stream_info.total_child_weights << ", expected "
+ << total_child_weights;
+ return false;
+ }
+ }
+ }
+
+ // Make sure num_streams reflects the total number of streams the map
+ // contains.
+ if (total_streams != num_streams()) {
+ SPDY_DLOG(INFO) << "Map contains incorrect number of streams.";
+ return false;
+ }
+ // Validate the validation function; we should have visited each stream twice
+ // (except for the root)
+ DCHECK(streams_visited == 2 * num_streams() - 1);
+ return true;
+}
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_HTTP2_PRIORITY_WRITE_SCHEDULER_H_
diff --git a/spdy/core/http2_priority_write_scheduler_test.cc b/spdy/core/http2_priority_write_scheduler_test.cc
new file mode 100644
index 0000000..17b7721
--- /dev/null
+++ b/spdy/core/http2_priority_write_scheduler_test.cc
@@ -0,0 +1,799 @@
+#include "net/third_party/quiche/src/spdy/core/http2_priority_write_scheduler.h"
+
+#include <initializer_list>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_test.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_test_helpers.h"
+
+using ::testing::AssertionFailure;
+using ::testing::AssertionResult;
+using ::testing::AssertionSuccess;
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::UnorderedElementsAre;
+
+namespace spdy {
+
+namespace test {
+
+template <typename StreamIdType>
+class Http2PriorityWriteSchedulerPeer {
+ public:
+ explicit Http2PriorityWriteSchedulerPeer(
+ Http2PriorityWriteScheduler<StreamIdType>* scheduler)
+ : scheduler_(scheduler) {}
+
+ int TotalChildWeights(StreamIdType stream_id) const {
+ return scheduler_->FindStream(stream_id)->total_child_weights;
+ }
+
+ bool ValidateInvariants() const {
+ return scheduler_->ValidateInvariantsForTests();
+ }
+
+ private:
+ Http2PriorityWriteScheduler<StreamIdType>* scheduler_;
+};
+
+class Http2PriorityWriteSchedulerTest : public ::testing::Test {
+ protected:
+ typedef uint32_t SpdyStreamId;
+
+ Http2PriorityWriteSchedulerTest() : peer_(&scheduler_) {}
+
+ Http2PriorityWriteScheduler<SpdyStreamId> scheduler_;
+ Http2PriorityWriteSchedulerPeer<SpdyStreamId> peer_;
+};
+
+TEST_F(Http2PriorityWriteSchedulerTest, RegisterAndUnregisterStreams) {
+ EXPECT_EQ(1, scheduler_.num_streams());
+ EXPECT_TRUE(scheduler_.StreamRegistered(0));
+ EXPECT_FALSE(scheduler_.StreamRegistered(1));
+
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ EXPECT_EQ(2, scheduler_.num_streams());
+ ASSERT_TRUE(scheduler_.StreamRegistered(1));
+ EXPECT_EQ(100, scheduler_.GetStreamPrecedence(1).weight());
+ EXPECT_FALSE(scheduler_.StreamRegistered(5));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(1));
+
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(0, 50, false));
+ // Should not be able to add a stream with an id that already exists.
+ EXPECT_SPDY_BUG(
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(1, 50, false)),
+ "Stream 5 already registered");
+ EXPECT_EQ(3, scheduler_.num_streams());
+ EXPECT_TRUE(scheduler_.StreamRegistered(1));
+ ASSERT_TRUE(scheduler_.StreamRegistered(5));
+ EXPECT_EQ(50, scheduler_.GetStreamPrecedence(5).weight());
+ EXPECT_FALSE(scheduler_.StreamRegistered(13));
+
+ scheduler_.RegisterStream(13, SpdyStreamPrecedence(5, 130, true));
+ EXPECT_EQ(4, scheduler_.num_streams());
+ EXPECT_TRUE(scheduler_.StreamRegistered(1));
+ EXPECT_TRUE(scheduler_.StreamRegistered(5));
+ ASSERT_TRUE(scheduler_.StreamRegistered(13));
+ EXPECT_EQ(130, scheduler_.GetStreamPrecedence(13).weight());
+ EXPECT_EQ(5, scheduler_.GetStreamPrecedence(13).parent_id());
+
+ scheduler_.UnregisterStream(5);
+ // Cannot remove a stream that has already been removed.
+ EXPECT_SPDY_BUG(scheduler_.UnregisterStream(5), "Stream 5 not registered");
+ EXPECT_EQ(3, scheduler_.num_streams());
+ EXPECT_TRUE(scheduler_.StreamRegistered(1));
+ EXPECT_FALSE(scheduler_.StreamRegistered(5));
+ EXPECT_TRUE(scheduler_.StreamRegistered(13));
+ EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamPrecedence(13).parent_id());
+
+ // The parent stream 19 doesn't exist, so this should use 0 as parent stream:
+ scheduler_.RegisterStream(7, SpdyStreamPrecedence(19, 70, false));
+ EXPECT_TRUE(scheduler_.StreamRegistered(7));
+ EXPECT_EQ(0, scheduler_.GetStreamPrecedence(7).parent_id());
+ // Now stream 7 already exists, so this should fail:
+ EXPECT_SPDY_BUG(
+ scheduler_.RegisterStream(7, SpdyStreamPrecedence(1, 70, false)),
+ "Stream 7 already registered");
+ // Try adding a second child to stream 13:
+ scheduler_.RegisterStream(17, SpdyStreamPrecedence(13, 170, false));
+
+ scheduler_.UpdateStreamPrecedence(17, SpdyStreamPrecedence(13, 150, false));
+ EXPECT_EQ(150, scheduler_.GetStreamPrecedence(17).weight());
+
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, RegisterStreamWithSpdy3Priority) {
+ EXPECT_FALSE(scheduler_.StreamRegistered(1));
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(3));
+ EXPECT_EQ(0, scheduler_.NumReadyStreams());
+ EXPECT_TRUE(scheduler_.StreamRegistered(1));
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+ EXPECT_EQ(147, scheduler_.GetStreamPrecedence(1).weight());
+ EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamPrecedence(1).parent_id());
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), IsEmpty());
+
+ EXPECT_SPDY_BUG(scheduler_.RegisterStream(1, SpdyStreamPrecedence(4)),
+ "Expected HTTP/2 stream dependency|"
+ "Stream 1 already registered");
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(1).spdy3_priority());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, GetStreamWeight) {
+ // Unknown streams tolerated due to b/15676312.
+ EXPECT_EQ(kHttp2MinStreamWeight, scheduler_.GetStreamPrecedence(3).weight());
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 130, true));
+ EXPECT_EQ(130, scheduler_.GetStreamPrecedence(3).weight());
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 50, true));
+ EXPECT_EQ(50, scheduler_.GetStreamPrecedence(3).weight());
+ scheduler_.UnregisterStream(3);
+ EXPECT_EQ(kHttp2MinStreamWeight, scheduler_.GetStreamPrecedence(3).weight());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, GetStreamPriority) {
+ // Unknown streams tolerated due to b/15676312.
+ EXPECT_EQ(kV3LowestPriority,
+ scheduler_.GetStreamPrecedence(3).spdy3_priority());
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 130, true));
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 50, true));
+ EXPECT_EQ(5, scheduler_.GetStreamPrecedence(3).spdy3_priority());
+ scheduler_.UnregisterStream(3);
+ EXPECT_EQ(kV3LowestPriority,
+ scheduler_.GetStreamPrecedence(3).spdy3_priority());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, GetStreamParent) {
+ // Unknown streams tolerated due to b/15676312.
+ EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamPrecedence(3).parent_id());
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 20, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(2, 30, false));
+ EXPECT_EQ(2, scheduler_.GetStreamPrecedence(3).parent_id());
+ scheduler_.UnregisterStream(3);
+ EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamPrecedence(3).parent_id());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, GetStreamChildren) {
+ EXPECT_SPDY_BUG(EXPECT_THAT(scheduler_.GetStreamChildren(7), IsEmpty()),
+ "Stream 7 not registered");
+ scheduler_.RegisterStream(7, SpdyStreamPrecedence(0, 70, false));
+ EXPECT_THAT(scheduler_.GetStreamChildren(7), IsEmpty());
+ scheduler_.RegisterStream(9, SpdyStreamPrecedence(7, 90, false));
+ scheduler_.RegisterStream(15, SpdyStreamPrecedence(7, 150, false));
+ EXPECT_THAT(scheduler_.GetStreamChildren(7), UnorderedElementsAre(9, 15));
+ scheduler_.UnregisterStream(7);
+ EXPECT_SPDY_BUG(EXPECT_THAT(scheduler_.GetStreamChildren(7), IsEmpty()),
+ "Stream 7 not registered");
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamWeight) {
+ EXPECT_SPDY_BUG(
+ scheduler_.UpdateStreamPrecedence(0, SpdyStreamPrecedence(0, 10, false)),
+ "Cannot set precedence of root stream");
+
+ // For the moment, updating stream precedence on a non-registered stream
+ // should have no effect. In the future, it will lazily cause the stream to
+ // be registered (b/15676312).
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 10, false));
+ EXPECT_FALSE(scheduler_.StreamRegistered(3));
+
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 10, false));
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 20, false));
+ EXPECT_EQ(20, scheduler_.GetStreamPrecedence(3).weight());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+
+ EXPECT_SPDY_BUG(
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 500, false)),
+ "Invalid weight: 500");
+ EXPECT_EQ(kHttp2MaxStreamWeight, scheduler_.GetStreamPrecedence(3).weight());
+ EXPECT_SPDY_BUG(
+ scheduler_.UpdateStreamPrecedence(3, SpdyStreamPrecedence(0, 0, false)),
+ "Invalid weight: 0");
+ EXPECT_EQ(kHttp2MinStreamWeight, scheduler_.GetStreamPrecedence(3).weight());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+
+ scheduler_.UnregisterStream(3);
+}
+
+// Basic case of reparenting a subtree.
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamParentBasicNonExclusive) {
+ /* Tree:
+ 0
+ / \
+ 1 2
+ / \
+ 3 4
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(2, 100, false));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(2));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), UnorderedElementsAre(3, 4));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+// Basic case of reparenting a subtree. Result here is the same as the
+// non-exclusive case.
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamParentBasicExclusive) {
+ /* Tree:
+ 0
+ / \
+ 1 2
+ / \
+ 3 4
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(2, 100, true));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(2));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), UnorderedElementsAre(3, 4));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+// We can't set the parent of a nonexistent stream, or set the parent to a
+// nonexistent stream.
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamParentNonexistent) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 100, false));
+ for (bool exclusive : {true, false}) {
+ // For the moment, updating stream precedence on a non-registered stream or
+ // attempting to set parent to a nonexistent stream should have no
+ // effect. In the future, it will lazily cause the stream(s) to be
+ // registered (b/15676312).
+
+ // No-op: parent stream 3 not registered
+ scheduler_.UpdateStreamPrecedence(1,
+ SpdyStreamPrecedence(3, 100, exclusive));
+
+ // No-op: stream 4 not registered
+ scheduler_.UpdateStreamPrecedence(4,
+ SpdyStreamPrecedence(2, 100, exclusive));
+
+ // No-op: stream 3 not registered
+ scheduler_.UpdateStreamPrecedence(3,
+ SpdyStreamPrecedence(4, 100, exclusive));
+
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), UnorderedElementsAre(1, 2));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), IsEmpty());
+ EXPECT_FALSE(scheduler_.StreamRegistered(3));
+ EXPECT_FALSE(scheduler_.StreamRegistered(4));
+ }
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+// We should be able to add multiple children to streams.
+TEST_F(Http2PriorityWriteSchedulerTest,
+ UpdateStreamParentMultipleChildrenNonExclusive) {
+ /* Tree:
+ 0
+ / \
+ 1 2
+ / \ \
+ 3 4 5
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.UpdateStreamPrecedence(2, SpdyStreamPrecedence(1, 100, false));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), UnorderedElementsAre(2, 3, 4));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), ElementsAre(5));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(5), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest,
+ UpdateStreamParentMultipleChildrenExclusive) {
+ /* Tree:
+ 0
+ / \
+ 1 2
+ / \ \
+ 3 4 5
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.UpdateStreamPrecedence(2, SpdyStreamPrecedence(1, 100, true));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), ElementsAre(2));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), UnorderedElementsAre(3, 4, 5));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(5), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamParentToChildNonExclusive) {
+ /* Tree:
+ 0
+ |
+ 1
+ / \
+ 2 3
+ |
+ 4
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(2, 100, false));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(2));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), ElementsAre(3));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), UnorderedElementsAre(1, 4));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamParentToChildExclusive) {
+ /* Tree:
+ 0
+ |
+ 1
+ / \
+ 2 3
+ |
+ 4
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(2, 100, true));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(2));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), UnorderedElementsAre(3, 4));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest,
+ UpdateStreamParentToGrandchildNonExclusive) {
+ /* Tree:
+ 0
+ |
+ 1
+ / \
+ 2 3
+ / \
+ 4 5
+ |
+ 6
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.RegisterStream(6, SpdyStreamPrecedence(4, 100, false));
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(4, 100, false));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(4));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), UnorderedElementsAre(2, 3));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), ElementsAre(5));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), UnorderedElementsAre(1, 6));
+ EXPECT_THAT(scheduler_.GetStreamChildren(5), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(6), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest,
+ UpdateStreamParentToGrandchildExclusive) {
+ /* Tree:
+ 0
+ |
+ 1
+ / \
+ 2 3
+ / \
+ 4 5
+ |
+ 6
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.RegisterStream(6, SpdyStreamPrecedence(4, 100, false));
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(4, 100, true));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(4));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), UnorderedElementsAre(2, 3, 6));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), ElementsAre(5));
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(4), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(5), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(6), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamParentToParent) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(1, 100, false));
+ for (bool exclusive : {true, false}) {
+ scheduler_.UpdateStreamPrecedence(2,
+ SpdyStreamPrecedence(1, 100, exclusive));
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), UnorderedElementsAre(2, 3));
+ EXPECT_THAT(scheduler_.GetStreamChildren(2), IsEmpty());
+ EXPECT_THAT(scheduler_.GetStreamChildren(3), IsEmpty());
+ }
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, UpdateStreamParentToSelf) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ EXPECT_SPDY_BUG(
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(1, 100, false)),
+ "Cannot set stream to be its own parent");
+ EXPECT_SPDY_BUG(
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(1, 100, true)),
+ "Cannot set stream to be its own parent");
+ EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(1));
+ EXPECT_THAT(scheduler_.GetStreamChildren(1), IsEmpty());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, BlockAndUnblock) {
+ /* Create the tree.
+
+ 0
+ / | \
+ / | \
+ 1 2 3
+ / \ \ \
+ 4 5 6 7
+ /| / \ | |\
+ 8 9 10 11 12 13 14
+ / \
+ 15 16
+
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(8, SpdyStreamPrecedence(4, 100, false));
+ scheduler_.RegisterStream(9, SpdyStreamPrecedence(4, 100, false));
+ scheduler_.RegisterStream(10, SpdyStreamPrecedence(5, 100, false));
+ scheduler_.RegisterStream(11, SpdyStreamPrecedence(5, 100, false));
+ scheduler_.RegisterStream(15, SpdyStreamPrecedence(8, 100, false));
+ scheduler_.RegisterStream(16, SpdyStreamPrecedence(8, 100, false));
+ scheduler_.RegisterStream(12, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.RegisterStream(6, SpdyStreamPrecedence(2, 100, true));
+ scheduler_.RegisterStream(7, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(13, SpdyStreamPrecedence(7, 100, true));
+ scheduler_.RegisterStream(14, SpdyStreamPrecedence(7, 100, false));
+ scheduler_.UpdateStreamPrecedence(7, SpdyStreamPrecedence(3, 100, false));
+ EXPECT_EQ(0, scheduler_.GetStreamPrecedence(1).parent_id());
+ EXPECT_EQ(0, scheduler_.GetStreamPrecedence(2).parent_id());
+ EXPECT_EQ(0, scheduler_.GetStreamPrecedence(3).parent_id());
+ EXPECT_EQ(1, scheduler_.GetStreamPrecedence(4).parent_id());
+ EXPECT_EQ(1, scheduler_.GetStreamPrecedence(5).parent_id());
+ EXPECT_EQ(2, scheduler_.GetStreamPrecedence(6).parent_id());
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(7).parent_id());
+ EXPECT_EQ(4, scheduler_.GetStreamPrecedence(8).parent_id());
+ EXPECT_EQ(4, scheduler_.GetStreamPrecedence(9).parent_id());
+ EXPECT_EQ(5, scheduler_.GetStreamPrecedence(10).parent_id());
+ EXPECT_EQ(5, scheduler_.GetStreamPrecedence(11).parent_id());
+ EXPECT_EQ(6, scheduler_.GetStreamPrecedence(12).parent_id());
+ EXPECT_EQ(7, scheduler_.GetStreamPrecedence(13).parent_id());
+ EXPECT_EQ(7, scheduler_.GetStreamPrecedence(14).parent_id());
+ EXPECT_EQ(8, scheduler_.GetStreamPrecedence(15).parent_id());
+ EXPECT_EQ(8, scheduler_.GetStreamPrecedence(16).parent_id());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+
+ EXPECT_EQ(peer_.TotalChildWeights(0),
+ scheduler_.GetStreamPrecedence(1).weight() +
+ scheduler_.GetStreamPrecedence(2).weight() +
+ scheduler_.GetStreamPrecedence(3).weight());
+ EXPECT_EQ(peer_.TotalChildWeights(3),
+ scheduler_.GetStreamPrecedence(7).weight());
+ EXPECT_EQ(peer_.TotalChildWeights(7),
+ scheduler_.GetStreamPrecedence(13).weight() +
+ scheduler_.GetStreamPrecedence(14).weight());
+ EXPECT_EQ(peer_.TotalChildWeights(13), 0);
+ EXPECT_EQ(peer_.TotalChildWeights(14), 0);
+
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, HasReadyStreams) {
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 10, false));
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ scheduler_.MarkStreamReady(1, false);
+ EXPECT_TRUE(scheduler_.HasReadyStreams());
+ EXPECT_TRUE(scheduler_.IsStreamReady(1));
+ scheduler_.MarkStreamNotReady(1);
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ EXPECT_FALSE(scheduler_.IsStreamReady(1));
+ scheduler_.MarkStreamReady(1, true);
+ EXPECT_TRUE(scheduler_.HasReadyStreams());
+ EXPECT_TRUE(scheduler_.IsStreamReady(1));
+ scheduler_.UnregisterStream(1);
+ EXPECT_FALSE(scheduler_.HasReadyStreams());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+ EXPECT_SPDY_BUG(scheduler_.IsStreamReady(1), "Stream 1 not registered");
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, CalculateRoundedWeights) {
+ /* Create the tree.
+
+ 0
+ / \
+ 1 2
+ /| |\ |\
+ 8 3 4 5 6 7
+ */
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 10, true));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 5, false));
+ scheduler_.RegisterStream(6, SpdyStreamPrecedence(2, 1, false));
+ scheduler_.RegisterStream(7, SpdyStreamPrecedence(2, 1, false));
+ scheduler_.RegisterStream(8, SpdyStreamPrecedence(1, 1, false));
+
+ // Remove higher-level streams.
+ scheduler_.UnregisterStream(1);
+ scheduler_.UnregisterStream(2);
+
+ // 3.3 rounded down = 3.
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(3).weight());
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(4).weight());
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(5).weight());
+ // 2.5 rounded up = 3.
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(6).weight());
+ EXPECT_EQ(3, scheduler_.GetStreamPrecedence(7).weight());
+ // 0 is not a valid weight, so round up to 1.
+ EXPECT_EQ(1, scheduler_.GetStreamPrecedence(8).weight());
+ ASSERT_TRUE(peer_.ValidateInvariants());
+}
+
+TEST_F(Http2PriorityWriteSchedulerTest, GetLatestEventWithPrecedence) {
+ EXPECT_SPDY_BUG(scheduler_.RecordStreamEventTime(3, 5),
+ "Stream 3 not registered");
+ EXPECT_SPDY_BUG(EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(4)),
+ "Stream 4 not registered");
+
+ for (int i = 1; i < 5; ++i) {
+ int weight = SpdyStreamPrecedence(i).weight();
+ scheduler_.RegisterStream(i, SpdyStreamPrecedence(0, weight, false));
+ }
+ for (int i = 1; i < 5; ++i) {
+ EXPECT_EQ(0, scheduler_.GetLatestEventWithPrecedence(i));
+ }
+ for (int i = 1; i < 5; ++i) {
+ scheduler_.RecordStreamEventTime(i, i * 100);
+ }
+ for (int i = 1; i < 5; ++i) {
+ EXPECT_EQ((i - 1) * 100, scheduler_.GetLatestEventWithPrecedence(i));
+ }
+}
+
+// Add ready streams at front and back.
+TEST_F(Http2PriorityWriteSchedulerTest, MarkReadyFrontAndBack) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 10, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 20, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 20, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(0, 20, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(0, 30, false));
+
+ for (int i = 1; i < 6; ++i) {
+ scheduler_.MarkStreamReady(i, false);
+ }
+ EXPECT_EQ(5, scheduler_.PopNextReadyStream());
+ EXPECT_EQ(2, scheduler_.PopNextReadyStream());
+ scheduler_.MarkStreamReady(2, false);
+ EXPECT_EQ(3, scheduler_.PopNextReadyStream());
+ scheduler_.MarkStreamReady(3, false);
+ EXPECT_EQ(4, scheduler_.PopNextReadyStream());
+ scheduler_.MarkStreamReady(4, false);
+ EXPECT_EQ(2, scheduler_.PopNextReadyStream());
+ scheduler_.MarkStreamReady(2, true);
+ EXPECT_EQ(2, scheduler_.PopNextReadyStream());
+ scheduler_.MarkStreamReady(5, false);
+ scheduler_.MarkStreamReady(2, true);
+ EXPECT_EQ(5, scheduler_.PopNextReadyStream());
+}
+
+// Add ready streams at front and back and pop them with
+// PopNextReadyStreamAndPrecedence.
+TEST_F(Http2PriorityWriteSchedulerTest, PopNextReadyStreamAndPrecedence) {
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 10, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 20, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 20, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(0, 20, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(0, 30, false));
+
+ for (int i = 1; i < 6; ++i) {
+ scheduler_.MarkStreamReady(i, false);
+ }
+ EXPECT_EQ(std::make_tuple(5, SpdyStreamPrecedence(0, 30, false)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+ EXPECT_EQ(std::make_tuple(2, SpdyStreamPrecedence(0, 20, false)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+ scheduler_.MarkStreamReady(2, false);
+ EXPECT_EQ(std::make_tuple(3, SpdyStreamPrecedence(0, 20, false)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+ scheduler_.MarkStreamReady(3, false);
+ EXPECT_EQ(std::make_tuple(4, SpdyStreamPrecedence(0, 20, false)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+ scheduler_.MarkStreamReady(4, false);
+ EXPECT_EQ(std::make_tuple(2, SpdyStreamPrecedence(0, 20, false)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+ scheduler_.MarkStreamReady(2, true);
+ EXPECT_EQ(std::make_tuple(2, SpdyStreamPrecedence(0, 20, false)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+ scheduler_.MarkStreamReady(5, false);
+ scheduler_.MarkStreamReady(2, true);
+ EXPECT_EQ(std::make_tuple(5, SpdyStreamPrecedence(0, 30, false)),
+ scheduler_.PopNextReadyStreamAndPrecedence());
+}
+
+class PopNextReadyStreamTest : public Http2PriorityWriteSchedulerTest {
+ protected:
+ void SetUp() override {
+ /* Create the tree.
+
+ 0
+ /|\
+ 1 2 3
+ /| |\
+ 4 5 6 7
+ /
+ 8
+
+ */
+ scheduler_.RegisterStream(1, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(2, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(3, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.RegisterStream(4, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(5, SpdyStreamPrecedence(1, 100, false));
+ scheduler_.RegisterStream(6, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.RegisterStream(7, SpdyStreamPrecedence(2, 100, false));
+ scheduler_.RegisterStream(8, SpdyStreamPrecedence(4, 100, false));
+
+ // Set all nodes ready to write.
+ for (SpdyStreamId id = 1; id <= 8; ++id) {
+ scheduler_.MarkStreamReady(id, false);
+ }
+ }
+
+ AssertionResult PopNextReturnsCycle(
+ std::initializer_list<SpdyStreamId> stream_ids) {
+ int count = 0;
+ const int kNumCyclesToCheck = 2;
+ for (int i = 0; i < kNumCyclesToCheck; i++) {
+ for (SpdyStreamId expected_id : stream_ids) {
+ SpdyStreamId next_id = scheduler_.PopNextReadyStream();
+ scheduler_.MarkStreamReady(next_id, false);
+ if (next_id != expected_id) {
+ return AssertionFailure() << "Pick " << count << ": expected stream "
+ << expected_id << " instead of " << next_id;
+ }
+ if (!peer_.ValidateInvariants()) {
+ return AssertionFailure() << "ValidateInvariants failed";
+ }
+ ++count;
+ }
+ }
+ return AssertionSuccess();
+ }
+};
+
+// When all streams are schedulable, only top-level streams should be returned.
+TEST_F(PopNextReadyStreamTest, NoneBlocked) {
+ EXPECT_TRUE(PopNextReturnsCycle({1, 2, 3}));
+}
+
+// When a parent stream is blocked, its children should be scheduled, if
+// priorities allow.
+TEST_F(PopNextReadyStreamTest, SingleStreamBlocked) {
+ scheduler_.MarkStreamNotReady(1);
+
+ // Round-robin only across 2 and 3, since children of 1 have lower priority.
+ EXPECT_TRUE(PopNextReturnsCycle({2, 3}));
+
+ // Make children of 1 have equal priority as 2 and 3, after which they should
+ // be returned as well.
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(0, 200, false));
+ EXPECT_TRUE(PopNextReturnsCycle({4, 5, 2, 3}));
+}
+
+// Block multiple levels of streams.
+TEST_F(PopNextReadyStreamTest, MultiLevelBlocked) {
+ for (SpdyStreamId stream_id : {1, 4, 5}) {
+ scheduler_.MarkStreamNotReady(stream_id);
+ }
+ // Round-robin only across 2 and 3, since children of 1 have lower priority.
+ EXPECT_TRUE(PopNextReturnsCycle({2, 3}));
+
+ // Make 8 have equal priority as 2 and 3.
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(0, 200, false));
+ EXPECT_TRUE(PopNextReturnsCycle({8, 2, 3}));
+}
+
+// A removed stream shouldn't be scheduled.
+TEST_F(PopNextReadyStreamTest, RemoveStream) {
+ scheduler_.UnregisterStream(1);
+
+ // Round-robin only across 2 and 3, since previous children of 1 have lower
+ // priority (the weight of 4 and 5 is scaled down when they are elevated to
+ // siblings of 2 and 3).
+ EXPECT_TRUE(PopNextReturnsCycle({2, 3}));
+
+ // Make previous children of 1 have equal priority as 2 and 3.
+ scheduler_.UpdateStreamPrecedence(4, SpdyStreamPrecedence(0, 100, false));
+ scheduler_.UpdateStreamPrecedence(5, SpdyStreamPrecedence(0, 100, false));
+ EXPECT_TRUE(PopNextReturnsCycle({4, 5, 2, 3}));
+}
+
+// Block an entire subtree.
+TEST_F(PopNextReadyStreamTest, SubtreeBlocked) {
+ for (SpdyStreamId stream_id : {1, 4, 5, 8}) {
+ scheduler_.MarkStreamNotReady(stream_id);
+ }
+ EXPECT_TRUE(PopNextReturnsCycle({2, 3}));
+}
+
+// If all parent streams are blocked, children should be returned.
+TEST_F(PopNextReadyStreamTest, ParentsBlocked) {
+ for (SpdyStreamId stream_id : {1, 2, 3}) {
+ scheduler_.MarkStreamNotReady(stream_id);
+ }
+ EXPECT_TRUE(PopNextReturnsCycle({4, 5, 6, 7}));
+}
+
+// Unblocking streams should make them schedulable.
+TEST_F(PopNextReadyStreamTest, BlockAndUnblock) {
+ EXPECT_TRUE(PopNextReturnsCycle({1, 2, 3}));
+ scheduler_.MarkStreamNotReady(2);
+ EXPECT_TRUE(PopNextReturnsCycle({1, 3}));
+ scheduler_.MarkStreamReady(2, false);
+ // Cycle order permuted since 2 effectively appended at tail.
+ EXPECT_TRUE(PopNextReturnsCycle({1, 3, 2}));
+}
+
+// Block nodes in multiple subtrees.
+TEST_F(PopNextReadyStreamTest, ScatteredBlocked) {
+ for (SpdyStreamId stream_id : {1, 2, 6, 7}) {
+ scheduler_.MarkStreamNotReady(stream_id);
+ }
+ // Only 3 returned, since of remaining streams it has highest priority.
+ EXPECT_TRUE(PopNextReturnsCycle({3}));
+
+ // Make children of 1 have priority equal to 3.
+ scheduler_.UpdateStreamPrecedence(1, SpdyStreamPrecedence(0, 200, false));
+ EXPECT_TRUE(PopNextReturnsCycle({4, 5, 3}));
+
+ // When 4 is blocked, its child 8 should take its place, since it has same
+ // priority.
+ scheduler_.MarkStreamNotReady(4);
+ EXPECT_TRUE(PopNextReturnsCycle({8, 5, 3}));
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/spdy/core/spdy_intrusive_list.h b/spdy/core/spdy_intrusive_list.h
new file mode 100644
index 0000000..16917f9
--- /dev/null
+++ b/spdy/core/spdy_intrusive_list.h
@@ -0,0 +1,326 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_
+#define QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_
+
+// A SpdyIntrusiveList<> is a doubly-linked list where the link pointers are
+// embedded in the elements. They are circularly linked making insertion and
+// removal into a known position constant time and branch-free operations.
+//
+// Usage is similar to an STL list<> where feasible, but there are important
+// differences. First and foremost, the elements must derive from the
+// SpdyIntrusiveLink<> base class:
+//
+// struct Foo : public SpdyIntrusiveLink<Foo> {
+// // ...
+// }
+//
+// SpdyIntrusiveList<Foo> l;
+// l.push_back(new Foo);
+// l.push_front(new Foo);
+// l.erase(&l.front());
+// l.erase(&l.back());
+//
+// Intrusive lists are primarily useful when you would have considered embedding
+// link pointers in your class directly for space or performance reasons. An
+// SpdyIntrusiveLink<> is the size of 2 pointers, usually 16 bytes on 64-bit
+// systems. Intrusive lists do not perform memory allocation (unlike the STL
+// list<> class) and thus may use less memory than list<>. In particular, if the
+// list elements are pointers to objects, using a list<> would perform an extra
+// memory allocation for each list node structure, while an SpdyIntrusiveList<>
+// would not.
+//
+// Note that SpdyIntrusiveLink is exempt from the C++ style guide's limitations
+// on multiple inheritance, so it's fine to inherit from both SpdyIntrusiveLink
+// and a base class, even if the base class is not a pure interface.
+//
+// Because the list pointers are embedded in the objects stored in an
+// SpdyIntrusiveList<>, erasing an item from a list is constant time. Consider
+// the following:
+//
+// map<string,Foo> foo_map;
+// list<Foo*> foo_list;
+//
+// foo_list.push_back(&foo_map["bar"]);
+// foo_list.erase(&foo_map["bar"]); // Compile error!
+//
+// The problem here is that a Foo* doesn't know where on foo_list it resides,
+// so removal requires iteration over the list. Various tricks can be performed
+// to overcome this. For example, a foo_list::iterator can be stored inside of
+// the Foo object. But at that point you'd be better off using an
+// SpdyIntrusiveList<>:
+//
+// map<string,Foo> foo_map;
+// SpdyIntrusiveList<Foo> foo_list;
+//
+// foo_list.push_back(&foo_map["bar"]);
+// foo_list.erase(&foo_map["bar"]); // Yeah!
+//
+// Note that SpdyIntrusiveLists come with a few limitations. The primary
+// limitation is that the SpdyIntrusiveLink<> base class is not copyable or
+// assignable. The result is that STL algorithms which mutate the order of
+// iterators, such as reverse() and unique(), will not work by default with
+// SpdyIntrusiveLists. In order to allow these algorithms to work you'll need to
+// define swap() and/or operator= for your class.
+//
+// Another limitation is that the SpdyIntrusiveList<> structure itself is not
+// copyable or assignable since an item/link combination can only exist on one
+// SpdyIntrusiveList<> at a time. This limitation is a result of the link
+// pointers for an item being intrusive in the item itself. For example, the
+// following will not compile:
+//
+// FooList a;
+// FooList b(a); // no copy constructor
+// b = a; // no assignment operator
+//
+// The similar STL code does work since the link pointers are external to the
+// item:
+//
+// list<int*> a;
+// a.push_back(new int);
+// list<int*> b(a);
+// CHECK(a.front() == b.front());
+//
+// Note that SpdyIntrusiveList::size() runs in O(N) time.
+
+#include <stddef.h>
+#include <iterator>
+
+
+namespace spdy {
+
+template <typename T, typename ListID> class SpdyIntrusiveList;
+
+template <typename T, typename ListID = void> class SpdyIntrusiveLink {
+ protected:
+ // We declare the constructor protected so that only derived types and the
+ // befriended list can construct this.
+ SpdyIntrusiveLink() : next_(nullptr), prev_(nullptr) {}
+
+#ifndef SWIG
+ SpdyIntrusiveLink(const SpdyIntrusiveLink&) = delete;
+ SpdyIntrusiveLink& operator=(const SpdyIntrusiveLink&) = delete;
+#endif // SWIG
+
+ private:
+ // We befriend the matching list type so that it can manipulate the links
+ // while they are kept private from others.
+ friend class SpdyIntrusiveList<T, ListID>;
+
+ // Encapsulates the logic to convert from a link to its derived type.
+ T* cast_to_derived() { return static_cast<T*>(this); }
+ const T* cast_to_derived() const { return static_cast<const T*>(this); }
+
+ SpdyIntrusiveLink* next_;
+ SpdyIntrusiveLink* prev_;
+};
+
+template <typename T, typename ListID = void> class SpdyIntrusiveList {
+ template <typename QualifiedT, typename QualifiedLinkT> class iterator_impl;
+
+ public:
+ typedef T value_type;
+ typedef value_type *pointer;
+ typedef const value_type *const_pointer;
+ typedef value_type &reference;
+ typedef const value_type &const_reference;
+ typedef size_t size_type;
+ typedef ptrdiff_t difference_type;
+
+ typedef SpdyIntrusiveLink<T, ListID> link_type;
+ typedef iterator_impl<T, link_type> iterator;
+ typedef iterator_impl<const T, const link_type> const_iterator;
+ typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
+ typedef std::reverse_iterator<iterator> reverse_iterator;
+
+ SpdyIntrusiveList() { clear(); }
+ // After the move constructor the moved-from list will be empty.
+ //
+ // NOTE: There is no move assign operator (for now).
+ // The reason is that at the moment 'clear()' does not unlink the nodes.
+ // It makes is_linked() return true when it should return false.
+ // If such node is removed from the list (e.g. from its destructor), or is
+ // added to another list - a memory corruption will occur.
+ // Admitedly the destructor does not unlink the nodes either, but move-assign
+ // will likely make the problem more prominent.
+#ifndef SWIG
+ SpdyIntrusiveList(SpdyIntrusiveList&& src) noexcept {
+ clear();
+ if (src.empty()) return;
+ sentinel_link_.next_ = src.sentinel_link_.next_;
+ sentinel_link_.prev_ = src.sentinel_link_.prev_;
+ // Fix head and tail nodes of the list.
+ sentinel_link_.prev_->next_ = &sentinel_link_;
+ sentinel_link_.next_->prev_ = &sentinel_link_;
+ src.clear();
+ }
+#endif // SWIG
+
+ iterator begin() { return iterator(sentinel_link_.next_); }
+ const_iterator begin() const { return const_iterator(sentinel_link_.next_); }
+ iterator end() { return iterator(&sentinel_link_); }
+ const_iterator end() const { return const_iterator(&sentinel_link_); }
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(end());
+ }
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rend() const {
+ return const_reverse_iterator(begin());
+ }
+
+ bool empty() const { return (sentinel_link_.next_ == &sentinel_link_); }
+ // This runs in O(N) time.
+ size_type size() const { return std::distance(begin(), end()); }
+ size_type max_size() const { return size_type(-1); }
+
+ reference front() { return *begin(); }
+ const_reference front() const { return *begin(); }
+ reference back() { return *(--end()); }
+ const_reference back() const { return *(--end()); }
+
+ static iterator insert(iterator position, T *obj) {
+ return insert_link(position.link(), obj);
+ }
+ void push_front(T* obj) { insert(begin(), obj); }
+ void push_back(T* obj) { insert(end(), obj); }
+
+ static iterator erase(T* obj) {
+ link_type* obj_link = obj;
+ // Fix up the next and previous links for the previous and next objects.
+ obj_link->next_->prev_ = obj_link->prev_;
+ obj_link->prev_->next_ = obj_link->next_;
+ // Zero out the next and previous links for the removed item. This will
+ // cause any future attempt to remove the item from the list to cause a
+ // crash instead of possibly corrupting the list structure.
+ link_type* next_link = obj_link->next_;
+ obj_link->next_ = nullptr;
+ obj_link->prev_ = nullptr;
+ return iterator(next_link);
+ }
+
+ static iterator erase(iterator position) {
+ return erase(position.operator->());
+ }
+ void pop_front() { erase(begin()); }
+ void pop_back() { erase(--end()); }
+
+ // Check whether the given element is linked into some list. Note that this
+ // does *not* check whether it is linked into a particular list.
+ // Also, if clear() is used to clear the containing list, is_linked() will
+ // still return true even though obj is no longer in any list.
+ static bool is_linked(const T* obj) {
+ return obj->link_type::next_ != nullptr;
+ }
+
+ void clear() {
+ sentinel_link_.next_ = sentinel_link_.prev_ = &sentinel_link_;
+ }
+ void swap(SpdyIntrusiveList& x) {
+ SpdyIntrusiveList tmp;
+ tmp.splice(tmp.begin(), *this);
+ this->splice(this->begin(), x);
+ x.splice(x.begin(), tmp);
+ }
+
+ void splice(iterator pos, SpdyIntrusiveList& src) {
+ splice(pos, src.begin(), src.end());
+ }
+
+ void splice(iterator pos, iterator i) { splice(pos, i, std::next(i)); }
+
+ void splice(iterator pos, iterator first, iterator last) {
+ if (first == last) return;
+
+ link_type* const last_prev = last.link()->prev_;
+
+ // Remove from the source.
+ first.link()->prev_->next_ = last.operator->();
+ last.link()->prev_ = first.link()->prev_;
+
+ // Attach to the destination.
+ first.link()->prev_ = pos.link()->prev_;
+ pos.link()->prev_->next_ = first.operator->();
+ last_prev->next_ = pos.operator->();
+ pos.link()->prev_ = last_prev;
+ }
+
+ private:
+ static iterator insert_link(link_type* next_link, T* obj) {
+ link_type* obj_link = obj;
+ obj_link->next_ = next_link;
+ link_type* const initial_next_prev = next_link->prev_;
+ obj_link->prev_ = initial_next_prev;
+ initial_next_prev->next_ = obj_link;
+ next_link->prev_ = obj_link;
+ return iterator(obj_link);
+ }
+
+ // The iterator implementation is parameterized on a potentially qualified
+ // variant of T and the matching qualified link type. Essentially, QualifiedT
+ // will either be 'T' or 'const T', the latter for a const_iterator.
+ template <typename QualifiedT, typename QualifiedLinkT>
+ class iterator_impl : public std::iterator<std::bidirectional_iterator_tag,
+ QualifiedT> {
+ public:
+ typedef std::iterator<std::bidirectional_iterator_tag, QualifiedT> base;
+
+ iterator_impl() : link_(nullptr) {}
+ iterator_impl(QualifiedLinkT* link) : link_(link) {}
+ iterator_impl(const iterator_impl& x) : link_(x.link_) {}
+
+ // Allow converting and comparing across iterators where the pointer
+ // assignment and comparisons (respectively) are allowed.
+ template <typename U, typename V>
+ iterator_impl(const iterator_impl<U, V>& x) : link_(x.link_) {}
+ template <typename U, typename V>
+ bool operator==(const iterator_impl<U, V>& x) const {
+ return link_ == x.link_;
+ }
+ template <typename U, typename V>
+ bool operator!=(const iterator_impl<U, V>& x) const {
+ return link_ != x.link_;
+ }
+
+ typename base::reference operator*() const { return *operator->(); }
+ typename base::pointer operator->() const {
+ return link_->cast_to_derived();
+ }
+
+ QualifiedLinkT *link() const { return link_; }
+
+#ifndef SWIG // SWIG can't wrap these operator overloads.
+ iterator_impl& operator++() { link_ = link_->next_; return *this; }
+ iterator_impl operator++(int /*unused*/) {
+ iterator_impl tmp = *this;
+ ++*this;
+ return tmp;
+ }
+ iterator_impl& operator--() { link_ = link_->prev_; return *this; }
+ iterator_impl operator--(int /*unused*/) {
+ iterator_impl tmp = *this;
+ --*this;
+ return tmp;
+ }
+#endif // SWIG
+
+ private:
+ // Ensure iterators can access other iterators node directly.
+ template <typename U, typename V> friend class iterator_impl;
+
+ QualifiedLinkT* link_;
+ };
+
+ // This bare link acts as the sentinel node.
+ link_type sentinel_link_;
+
+ // These are private and undefined to prevent copying and assigning.
+ SpdyIntrusiveList(const SpdyIntrusiveList&);
+ void operator=(const SpdyIntrusiveList&);
+};
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_CORE_SPDY_INTRUSIVE_LIST_H_
diff --git a/spdy/core/spdy_intrusive_list_test.cc b/spdy/core/spdy_intrusive_list_test.cc
new file mode 100644
index 0000000..eee65e3
--- /dev/null
+++ b/spdy/core/spdy_intrusive_list_test.cc
@@ -0,0 +1,427 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "net/third_party/quiche/src/spdy/core/spdy_intrusive_list.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <list>
+#include <utility>
+
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_string.h"
+#include "net/third_party/quiche/src/spdy/platform/api/spdy_test.h"
+
+namespace spdy {
+namespace test {
+
+struct ListId2 {};
+
+struct TestItem : public SpdyIntrusiveLink<TestItem>,
+ public SpdyIntrusiveLink<TestItem, ListId2> {
+ int n;
+};
+typedef SpdyIntrusiveList<TestItem> TestList;
+typedef std::list<TestItem*> CanonicalList;
+
+void swap(TestItem &a, TestItem &b) {
+ using std::swap;
+ swap(a.n, b.n);
+}
+
+class IntrusiveListTest : public ::testing::Test {
+ protected:
+ void CheckLists() {
+ CheckLists(l1, ll1);
+ if (::testing::Test::HasFailure()) return;
+ CheckLists(l2, ll2);
+ }
+
+ void CheckLists(const TestList &list_a, const CanonicalList &list_b) {
+ ASSERT_EQ(list_a.size(), list_b.size());
+ TestList::const_iterator it_a = list_a.begin();
+ CanonicalList::const_iterator it_b = list_b.begin();
+ while (it_a != list_a.end()) {
+ EXPECT_EQ(&*it_a++, *it_b++);
+ }
+ EXPECT_EQ(list_a.end(), it_a);
+ EXPECT_EQ(list_b.end(), it_b);
+ }
+
+ void PrepareLists(int num_elems_1, int num_elems_2 = 0) {
+ FillLists(&l1, &ll1, e, num_elems_1);
+ FillLists(&l2, &ll2, e + num_elems_1, num_elems_2);
+ }
+
+ void FillLists(TestList *list_a, CanonicalList *list_b, TestItem *elems,
+ int num_elems) {
+ list_a->clear();
+ list_b->clear();
+ for (int i = 0; i < num_elems; ++i) {
+ list_a->push_back(elems + i);
+ list_b->push_back(elems + i);
+ }
+ CheckLists(*list_a, *list_b);
+ }
+
+ TestItem e[10];
+ TestList l1, l2;
+ CanonicalList ll1, ll2;
+};
+
+TEST(NewIntrusiveListTest, Basic) {
+ TestList list1;
+
+ CHECK_EQ(sizeof(SpdyIntrusiveLink<TestItem>), sizeof(void *) * 2);
+
+ for (int i = 0; i < 10; ++i) {
+ TestItem *e = new TestItem;
+ e->n = i;
+ list1.push_front(e);
+ }
+ CHECK_EQ(list1.size(), 10);
+
+ // Verify we can reverse a list because we defined swap for TestItem.
+ std::reverse(list1.begin(), list1.end());
+ CHECK_EQ(list1.size(), 10);
+
+ // Check both const and non-const forward iteration.
+ const TestList& clist1 = list1;
+ int i = 0;
+ TestList::iterator iter = list1.begin();
+ for (;
+ iter != list1.end();
+ ++iter, ++i) {
+ CHECK_EQ(iter->n, i);
+ }
+ CHECK(iter == clist1.end());
+ CHECK(iter != clist1.begin());
+ i = 0;
+ iter = list1.begin();
+ for (;
+ iter != list1.end();
+ ++iter, ++i) {
+ CHECK_EQ(iter->n, i);
+ }
+ CHECK(iter == clist1.end());
+ CHECK(iter != clist1.begin());
+
+ CHECK_EQ(list1.front().n, 0);
+ CHECK_EQ(list1.back().n, 9);
+
+ // Verify we can swap 2 lists.
+ TestList list2;
+ list2.swap(list1);
+ CHECK_EQ(list1.size(), 0);
+ CHECK_EQ(list2.size(), 10);
+
+ // Check both const and non-const reverse iteration.
+ const TestList& clist2 = list2;
+ TestList::reverse_iterator riter = list2.rbegin();
+ i = 9;
+ for (;
+ riter != list2.rend();
+ ++riter, --i) {
+ CHECK_EQ(riter->n, i);
+ }
+ CHECK(riter == clist2.rend());
+ CHECK(riter != clist2.rbegin());
+
+ riter = list2.rbegin();
+ i = 9;
+ for (;
+ riter != list2.rend();
+ ++riter, --i) {
+ CHECK_EQ(riter->n, i);
+ }
+ CHECK(riter == clist2.rend());
+ CHECK(riter != clist2.rbegin());
+
+ while (!list2.empty()) {
+ TestItem *e = &list2.front();
+ list2.pop_front();
+ delete e;
+ }
+}
+
+TEST(NewIntrusiveListTest, Erase) {
+ TestList l;
+ TestItem *e[10];
+
+ // Create a list with 10 items.
+ for (int i = 0; i < 10; ++i) {
+ e[i] = new TestItem;
+ l.push_front(e[i]);
+ }
+
+ // Test that erase works.
+ for (int i = 0; i < 10; ++i) {
+ CHECK_EQ(l.size(), (10 - i));
+
+ TestList::iterator iter = l.erase(e[i]);
+ CHECK(iter != TestList::iterator(e[i]));
+
+ CHECK_EQ(l.size(), (10 - i - 1));
+ delete e[i];
+ }
+}
+
+TEST(NewIntrusiveListTest, Insert) {
+ TestList l;
+ TestList::iterator iter = l.end();
+ TestItem *e[10];
+
+ // Create a list with 10 items.
+ for (int i = 9; i >= 0; --i) {
+ e[i] = new TestItem;
+ iter = l.insert(iter, e[i]);
+ CHECK_EQ(&(*iter), e[i]);
+ }
+
+ CHECK_EQ(l.size(), 10);
+
+ // Verify insertion order.
+ iter = l.begin();
+ for (TestItem *item : e) {
+ CHECK_EQ(&(*iter), item);
+ iter = l.erase(item);
+ delete item;
+ }
+}
+
+TEST(NewIntrusiveListTest, Move) {
+ // Move contructible.
+
+ { // Move-construct from an empty list.
+ TestList src;
+ TestList dest(std::move(src));
+ EXPECT_TRUE(dest.empty());
+ }
+
+ { // Move-construct from a single item list.
+ TestItem e;
+ TestList src;
+ src.push_front(&e);
+
+ TestList dest(std::move(src));
+ EXPECT_TRUE(src.empty()); // NOLINT bugprone-use-after-move
+ ASSERT_THAT(dest.size(), 1);
+ EXPECT_THAT(&dest.front(), &e);
+ EXPECT_THAT(&dest.back(), &e);
+ }
+
+ { // Move-construct from a list with multiple items.
+ TestItem items[10];
+ TestList src;
+ for (TestItem &e : items) src.push_back(&e);
+
+ TestList dest(std::move(src));
+ EXPECT_TRUE(src.empty()); // NOLINT bugprone-use-after-move
+ // Verify the items on the destination list.
+ ASSERT_THAT(dest.size(), 10);
+ int i = 0;
+ for (TestItem &e : dest) {
+ EXPECT_THAT(&e, &items[i++]) << " for index " << i;
+ }
+ }
+}
+
+TEST(NewIntrusiveListTest, StaticInsertErase) {
+ TestList l;
+ TestItem e[2];
+ TestList::iterator i = l.begin();
+ TestList::insert(i, &e[0]);
+ TestList::insert(&e[0], &e[1]);
+ TestList::erase(&e[0]);
+ TestList::erase(TestList::iterator(&e[1]));
+ EXPECT_TRUE(l.empty());
+}
+
+TEST_F(IntrusiveListTest, Splice) {
+ // We verify that the contents of this secondary list aren't affected by any
+ // of the splices.
+ SpdyIntrusiveList<TestItem, ListId2> secondary_list;
+ for (int i = 0; i < 3; ++i) {
+ secondary_list.push_back(&e[i]);
+ }
+
+ // Test the basic cases:
+ // - The lists range from 0 to 2 elements.
+ // - The insertion point ranges from begin() to end()
+ // - The transfered range has multiple sizes and locations in the source.
+ for (int l1_count = 0; l1_count < 3; ++l1_count) {
+ for (int l2_count = 0; l2_count < 3; ++l2_count) {
+ for (int pos = 0; pos <= l1_count; ++pos) {
+ for (int first = 0; first <= l2_count; ++first) {
+ for (int last = first; last <= l2_count; ++last) {
+ PrepareLists(l1_count, l2_count);
+
+ l1.splice(std::next(l1.begin(), pos), std::next(l2.begin(), first),
+ std::next(l2.begin(), last));
+ ll1.splice(std::next(ll1.begin(), pos), ll2,
+ std::next(ll2.begin(), first),
+ std::next(ll2.begin(), last));
+
+ CheckLists();
+
+ ASSERT_EQ(3, secondary_list.size());
+ for (int i = 0; i < 3; ++i) {
+ EXPECT_EQ(&e[i], &*std::next(secondary_list.begin(), i));
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+// Build up a set of classes which form "challenging" type hierarchies to use
+// with an SpdyIntrusiveList.
+struct BaseLinkId {};
+struct DerivedLinkId {};
+
+struct AbstractBase : public SpdyIntrusiveLink<AbstractBase, BaseLinkId> {
+ virtual ~AbstractBase() = 0;
+ virtual SpdyString name() { return "AbstractBase"; }
+};
+AbstractBase::~AbstractBase() {}
+struct DerivedClass : public SpdyIntrusiveLink<DerivedClass, DerivedLinkId>,
+ public AbstractBase {
+ virtual ~DerivedClass() {}
+ virtual SpdyString name() { return "DerivedClass"; }
+};
+struct VirtuallyDerivedBaseClass : public virtual AbstractBase {
+ virtual ~VirtuallyDerivedBaseClass() = 0;
+ virtual SpdyString name() { return "VirtuallyDerivedBaseClass"; }
+};
+VirtuallyDerivedBaseClass::~VirtuallyDerivedBaseClass() {}
+struct VirtuallyDerivedClassA
+ : public SpdyIntrusiveLink<VirtuallyDerivedClassA, DerivedLinkId>,
+ public virtual VirtuallyDerivedBaseClass {
+ virtual ~VirtuallyDerivedClassA() {}
+ virtual SpdyString name() { return "VirtuallyDerivedClassA"; }
+};
+struct NonceClass {
+ virtual ~NonceClass() {}
+ int data_;
+};
+struct VirtuallyDerivedClassB
+ : public SpdyIntrusiveLink<VirtuallyDerivedClassB, DerivedLinkId>,
+ public virtual NonceClass,
+ public virtual VirtuallyDerivedBaseClass {
+ virtual ~VirtuallyDerivedClassB() {}
+ virtual SpdyString name() { return "VirtuallyDerivedClassB"; }
+};
+struct VirtuallyDerivedClassC
+ : public SpdyIntrusiveLink<VirtuallyDerivedClassC, DerivedLinkId>,
+ public virtual AbstractBase,
+ public virtual NonceClass,
+ public virtual VirtuallyDerivedBaseClass {
+ virtual ~VirtuallyDerivedClassC() {}
+ virtual SpdyString name() { return "VirtuallyDerivedClassC"; }
+};
+
+// Test for multiple layers between the element type and the link.
+namespace templated_base_link {
+template <typename T> struct AbstractBase : public SpdyIntrusiveLink<T> {
+ virtual ~AbstractBase() = 0;
+};
+template <typename T> AbstractBase<T>::~AbstractBase() {}
+struct DerivedClass : public AbstractBase<DerivedClass> {
+ int n;
+};
+}
+
+TEST(NewIntrusiveListTest, HandleInheritanceHierarchies) {
+ {
+ SpdyIntrusiveList<DerivedClass, DerivedLinkId> list;
+ DerivedClass elements[2];
+ EXPECT_TRUE(list.empty());
+ list.push_back(&elements[0]);
+ EXPECT_EQ(1, list.size());
+ list.push_back(&elements[1]);
+ EXPECT_EQ(2, list.size());
+ list.pop_back();
+ EXPECT_EQ(1, list.size());
+ list.pop_back();
+ EXPECT_TRUE(list.empty());
+ }
+ {
+ SpdyIntrusiveList<VirtuallyDerivedClassA, DerivedLinkId> list;
+ VirtuallyDerivedClassA elements[2];
+ EXPECT_TRUE(list.empty());
+ list.push_back(&elements[0]);
+ EXPECT_EQ(1, list.size());
+ list.push_back(&elements[1]);
+ EXPECT_EQ(2, list.size());
+ list.pop_back();
+ EXPECT_EQ(1, list.size());
+ list.pop_back();
+ EXPECT_TRUE(list.empty());
+ }
+ {
+ SpdyIntrusiveList<VirtuallyDerivedClassC, DerivedLinkId> list;
+ VirtuallyDerivedClassC elements[2];
+ EXPECT_TRUE(list.empty());
+ list.push_back(&elements[0]);
+ EXPECT_EQ(1, list.size());
+ list.push_back(&elements[1]);
+ EXPECT_EQ(2, list.size());
+ list.pop_back();
+ EXPECT_EQ(1, list.size());
+ list.pop_back();
+ EXPECT_TRUE(list.empty());
+ }
+ {
+ SpdyIntrusiveList<AbstractBase, BaseLinkId> list;
+ DerivedClass d1;
+ VirtuallyDerivedClassA d2;
+ VirtuallyDerivedClassB d3;
+ VirtuallyDerivedClassC d4;
+ EXPECT_TRUE(list.empty());
+ list.push_back(&d1);
+ EXPECT_EQ(1, list.size());
+ list.push_back(&d2);
+ EXPECT_EQ(2, list.size());
+ list.push_back(&d3);
+ EXPECT_EQ(3, list.size());
+ list.push_back(&d4);
+ EXPECT_EQ(4, list.size());
+ SpdyIntrusiveList<AbstractBase, BaseLinkId>::iterator it = list.begin();
+ EXPECT_EQ("DerivedClass", (it++)->name());
+ EXPECT_EQ("VirtuallyDerivedClassA", (it++)->name());
+ EXPECT_EQ("VirtuallyDerivedClassB", (it++)->name());
+ EXPECT_EQ("VirtuallyDerivedClassC", (it++)->name());
+ }
+ {
+ SpdyIntrusiveList<templated_base_link::DerivedClass> list;
+ templated_base_link::DerivedClass elements[2];
+ EXPECT_TRUE(list.empty());
+ list.push_back(&elements[0]);
+ EXPECT_EQ(1, list.size());
+ list.push_back(&elements[1]);
+ EXPECT_EQ(2, list.size());
+ list.pop_back();
+ EXPECT_EQ(1, list.size());
+ list.pop_back();
+ EXPECT_TRUE(list.empty());
+ }
+}
+
+
+class IntrusiveListTagTypeTest : public testing::Test {
+ protected:
+ struct Tag {};
+ class Element : public SpdyIntrusiveLink<Element, Tag> {};
+};
+
+TEST_F(IntrusiveListTagTypeTest, TagTypeListID) {
+ SpdyIntrusiveList<Element, Tag> list;
+ {
+ Element e;
+ list.push_back(&e);
+ }
+}
+
+} // namespace test
+} // namespace spdy
diff --git a/spdy/platform/api/spdy_containers.h b/spdy/platform/api/spdy_containers.h
index e4f4c49..9a90556 100644
--- a/spdy/platform/api/spdy_containers.h
+++ b/spdy/platform/api/spdy_containers.h
@@ -37,6 +37,12 @@
return SpdyHashStringPairImpl(a, b);
}
+// Used for maps that are typically small, then it is faster than (for example)
+// hash_map which is optimized for large data sets. SpdySmallMap upgrades itself
+// automatically to a SpdySmallMapImpl-specified map when it runs out of space.
+template <typename Key, typename Value, int Size>
+using SpdySmallMap = SpdySmallMapImpl<Key, Value, Size>;
+
} // namespace spdy
#endif // QUICHE_SPDY_PLATFORM_API_SPDY_CONTAINERS_H_
diff --git a/spdy/platform/api/spdy_map_util.h b/spdy/platform/api/spdy_map_util.h
new file mode 100644
index 0000000..e9b5f85
--- /dev/null
+++ b/spdy/platform/api/spdy_map_util.h
@@ -0,0 +1,19 @@
+// Copyright (c) 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_SPDY_PLATFORM_API_SPDY_MAP_UTIL_H_
+#define QUICHE_SPDY_PLATFORM_API_SPDY_MAP_UTIL_H_
+
+#include "net/spdy/platform/impl/spdy_map_util_impl.h"
+
+namespace spdy {
+
+template <class Collection, class Key>
+bool SpdyContainsKey(const Collection& collection, const Key& key) {
+ return SpdyContainsKeyImpl(collection, key);
+}
+
+} // namespace spdy
+
+#endif // QUICHE_SPDY_PLATFORM_API_SPDY_MAP_UTIL_H_