diff --git a/net/spdy/http2_write_scheduler.h b/net/spdy/http2_write_scheduler.h index e2b0372e12bb63..2bf5aee0cf5112 100644 --- a/net/spdy/http2_write_scheduler.h +++ b/net/spdy/http2_write_scheduler.h @@ -5,15 +5,20 @@ #ifndef NET_SPDY_HTTP2_WRITE_SCHEDULER_H_ #define NET_SPDY_HTTP2_WRITE_SCHEDULER_H_ +#include + +#include #include #include #include #include #include +#include #include #include #include "base/containers/hash_tables.h" +#include "base/containers/linked_list.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" @@ -25,240 +30,314 @@ namespace net { // section 5.3 of RFC 7540: // http://tools.ietf.org/html/rfc7540#section-5.3 // -// Nodes can be added and removed, and dependencies between them defined. -// Nodes constitute a tree rooted at node ID 0: each node has a single parent -// node, and 0 or more child nodes. Individual nodes can be marked as ready to -// read/write, and then the whole structure can be queried to pick the next -// node to read/write out of those that are ready. +// 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. // -// The NodeId type must be a POD that supports comparison (most +// The StreamIdType type must be a POD that supports comparison (most // likely, it will be a number). namespace test { -template -class SpdyPriorityTreePeer; +template +class Http2PriorityWriteSchedulerPeer; } -const int kRootNodeId = 0; -const int kDefaultWeight = 16; -const int kMinWeight = 1; -const int kMaxWeight = 256; +const unsigned int kHttp2RootStreamId = 0; +const int kHttp2DefaultStreamWeight = 16; +const int kHttp2MinStreamWeight = 1; +const int kHttp2MaxStreamWeight = 256; -template -class SpdyPriorityTree { +template +class Http2PriorityWriteScheduler { public: - typedef std::pair PriorityNode; - typedef std::vector PriorityList; - - SpdyPriorityTree(); - - // Orders in descending order of priority. - struct NodePriorityComparator { - bool operator ()(const std::pair& lhs, - const std::pair& rhs); - }; - - friend class test::SpdyPriorityTreePeer; - - // Return the number of nodes currently in the tree. - int num_nodes() const; - - // Return true if the tree contains a node with the given ID. - bool NodeExists(NodeId node_id) const; - - // Add a new node with the given weight and parent. Non-exclusive nodes - // simply get added below the parent node. If exclusive = true, the node - // becomes the parent's sole child and the parent's previous children - // become the children of the new node. - // Returns true on success. Returns false if the node already exists - // in the tree, or if the parent node does not exist. - bool AddNode(NodeId node_id, NodeId parent_id, int weight, bool exclusive); - - // Remove an existing node from the tree. Returns true on success, or - // false if the node doesn't exist. - bool RemoveNode(NodeId node_id); - - // Get the weight of the given node. - int GetWeight(NodeId node_id) const; - - // Get the parent of the given node. If the node doesn't exist, or is a root - // node (and thus has no parent), returns NodeId(). - NodeId GetParent(NodeId node_id) const; - - // Get the children of the given node. If the node doesn't exist, or has no - // child, returns empty vector. - std::vector GetChildren(NodeId node_id) const; - - // Set the priority of the given node. - bool SetWeight(NodeId node_id, int weight); - - // Set the parent of the given node. Returns true on success. - // Returns false and has no effect if the node and/or the parent doesn't - // exist. If the new parent is a descendant of the node (i.e. this would have - // created a cycle) then we rearrange the topology of the tree as described - // in section 5.3.3 of RFC 7540: - // https://tools.ietf.org/html/rfc7540#section-5.3.3 - bool SetParent(NodeId node_id, NodeId parent_id, bool exclusive); - - // Returns true if the node parent_id has child_id in its children. - bool HasChild(NodeId parent_id, NodeId child_id) const; - - // Mark a node as blocked or unblocked. Return true on success, or false - // if unable to mark the specified node. - bool SetBlocked(NodeId node_id, bool blocked); - - // Mark whether or not a node is ready to write; i.e. whether there is - // buffered data for the associated stream. Return true on success, or false - // if unable to mark the specified node. - bool SetReady(NodeId node_id, bool ready); - - // Returns an ordered list of writeable nodes and their priorities. - // Priority is calculated as: - // parent's priority * (node's weight / sum of sibling weights) - PriorityList GetPriorityList(); + Http2PriorityWriteScheduler(); + + friend class test::Http2PriorityWriteSchedulerPeer; + + // Return the number of streams currently in the tree. + int num_streams() const; + + // Return true if the tree contains a stream with the given ID. + bool StreamRegistered(StreamIdType stream_id) const; + + // Registers a new stream with the given weight and parent, adding it to the + // dependency tree. Non-exclusive streams simply get added below the parent + // stream. If exclusive = true, the stream becomes the parent's sole child + // and the parent's previous children become the children of the new + // stream. If the stream was already registered, logs DFATAL and does + // nothing. If the parent stream is not registered, logs DFATAL and uses the + // root stream as the parent. + void RegisterStream(StreamIdType stream_id, + StreamIdType parent_id, + int weight, + bool exclusive); + + // Unregisters the given stream from the scheduler, removing it from the + // dependency tree. If the stream was not previously registered, logs DFATAL + // and does nothing. + void UnregisterStream(StreamIdType stream_id); + + // Returns the weight value for the specified stream. If the stream is not + // registered, logs DFATAL and returns the lowest weight. + int GetStreamWeight(StreamIdType stream_id) const; + + // Returns the stream ID for the parent of the given stream. If the stream + // isn't registered, logs DFATAL and returns the root stream ID (0). + StreamIdType GetStreamParent(StreamIdType stream_id) const; + + // Returns stream IDs of the children of the given stream, if any. If the + // stream isn't registered, logs DFATAL and returns an empty vector. + std::vector GetStreamChildren(StreamIdType stream_id) const; + + // Sets the weight of the given stream. If the stream isn't registered or is + // the root stream, logs DFATAL and does nothing. + void SetStreamWeight(StreamIdType stream_id, int weight); + + // Sets the parent of the given stream. If the stream and/or parent aren't + // registered, logs DFATAL and does nothing. If the new parent is a + // descendant of the stream (i.e. this would have created a cycle) then the + // topology of the tree is rearranged as described in section 5.3.3 of RFC + // 7540: https://tools.ietf.org/html/rfc7540#section-5.3.3 + void SetStreamParent(StreamIdType stream_id, + StreamIdType parent_id, + bool exclusive); + + // Returns true if the stream parent_id has child_id in its children. If + // either parent or child stream aren't registered, logs DFATAL and returns + // false. + bool StreamHasChild(StreamIdType parent_id, StreamIdType child_id) const; + + // Marks the stream as blocked or unblocked. If the stream is not registered, + // logs DFATAL and does nothing. + void MarkStreamBlocked(StreamIdType stream_id, bool blocked); + + // Marks the stream as ready or not ready to write; i.e. whether there is + // buffered data for the associated stream. If the stream is not registered, + // logs DFATAL and does nothing. + void MarkStreamReady(StreamIdType stream_id, bool ready); + + // Returns true iff the scheduler has one or more usable streams. A stream is + // usable if it has ready == true and blocked == false, and is not the direct + // or indirect child of another stream that itself has ready == true and + // blocked == false. (Note that the root stream always has ready == false.) + bool HasUsableStreams() const; + + // If the scheduler has any usable streams, returns the ID of the next usable + // stream, in the process changing its ready state to false. If the scheduler + // does not have any usable streams, logs DFATAL and returns the root stream + // ID (0). If there are multiple usable streams, precedence is given to the + // one with the highest priority (thus preserving SPDY priority semantics), + // or, if there are multiple with the highest priority, the one with the + // lowest ordinal (ensuring round-robin ordering). + StreamIdType PopNextUsableStream(); private: - struct Node; - typedef std::vector NodeVector; - typedef std::map NodeMap; - - struct Node { - // ID for this node. - NodeId id; - // ID of parent node. - Node* parent = nullptr; + struct StreamInfo; + using StreamInfoVector = std::vector; + using StreamInfoMap = std::unordered_map; + + struct StreamInfo : public base::LinkNode { + // ID for this stream. + StreamIdType id; + // ID of parent stream. + StreamInfo* parent = nullptr; // Weights can range between 1 and 256 (inclusive). - int weight = kDefaultWeight; - // The total weight of this node's direct descendants. + int weight = kHttp2DefaultStreamWeight; + // The total weight of this stream's direct descendants. int total_child_weights = 0; - // The total weight of direct descendants that are writeable - // (ready to write and not blocked). This value does not necessarily - // reflect the current state of the tree; instead, we lazily update it - // on calls to PropagateNodeState(). - int total_writeable_child_weights = 0; - // Pointers to nodes for children, if any. - NodeVector children; + // Pointers to StreamInfos for children, if any. + StreamInfoVector children; // Is the associated stream write-blocked? bool blocked = false; // Does the stream have data ready for writing? bool ready = false; - // The fraction of resources to dedicate to this node. + // Whether the stream is currently present in scheduling_queue_. + bool scheduled = false; + // The scheduling priority of this stream. Streams with higher priority + // values are scheduled first. 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. The ordinal is reset to a new, greater + // value when the stream is next inserted into scheduling_queue_. + int64_t ordinal = 0; + + // Whether the stream ought to be in scheduling_queue_. + bool IsSchedulable() const { return ready && !blocked; } + + // 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; + } }; - static bool Remove(NodeVector* nodes, const Node* node); + static bool Remove(StreamInfoVector* stream_infos, + const StreamInfo* stream_info); + + // Clamps weight to a value in [kHttp2MinStreamWeight, + // kHttp2MaxStreamWeight]. + static int ClampWeight(int weight); + + // Returns true iff any direct or transitive parent of the given stream is + // currently scheduled. + static bool HasScheduledAncestor(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); + + // 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_. + void UpdatePrioritiesUnder(StreamInfo* stream_info); - // Update the value of total_writeable_child_weights for the given node - // to reflect the current state of the tree. - void PropagateNodeState(Node* node); + // Adds or removes stream from scheduling_queue_ according to whether it is + // schedulable. If stream is newly schedulable, assigns it the next + // (increasing) ordinal value. + void UpdateScheduling(StreamInfo* stream_info); - // Get the given node, or return nullptr if it doesn't exist. - const Node* FindNode(NodeId node_id) const; - Node* FindNode(NodeId node_id); + // 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; - Node* root_node_; // pointee owned by all_nodes_ - NodeMap all_nodes_; // maps from node IDs to Node objects - STLValueDeleter all_nodes_deleter_; - - DISALLOW_COPY_AND_ASSIGN(SpdyPriorityTree); + // Pointee owned by all_stream_infos_. + StreamInfo* root_stream_info_; + // Maps from stream IDs to StreamInfo objects. + StreamInfoMap all_stream_infos_; + STLValueDeleter all_stream_infos_deleter_; + // Queue containing all streams that are ready and unblocked, 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 + // necessarily usable: 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. + base::LinkedList scheduling_queue_; + // Ordinal value to assign to next node inserted into + // scheduling_queue_. Incremented after each insertion. + int64_t next_ordinal_ = 0; + + DISALLOW_COPY_AND_ASSIGN(Http2PriorityWriteScheduler); }; -template -SpdyPriorityTree::SpdyPriorityTree() - : all_nodes_deleter_(&all_nodes_) { - root_node_ = new Node(); - root_node_->id = kRootNodeId; - root_node_->weight = kDefaultWeight; - root_node_->parent = nullptr; - root_node_->priority = 1.0; - root_node_->ready = true; - all_nodes_[kRootNodeId] = root_node_; -} - -template -bool SpdyPriorityTree::NodePriorityComparator::operator()( - const std::pair& lhs, - const std::pair& rhs) { - return lhs.second > rhs.second; +template +Http2PriorityWriteScheduler::Http2PriorityWriteScheduler() + : all_stream_infos_deleter_(&all_stream_infos_) { + root_stream_info_ = new StreamInfo(); + 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 = true; + all_stream_infos_[kHttp2RootStreamId] = root_stream_info_; } -template -int SpdyPriorityTree::num_nodes() const { - return all_nodes_.size(); +template +int Http2PriorityWriteScheduler::num_streams() const { + return all_stream_infos_.size(); } -template -bool SpdyPriorityTree::NodeExists(NodeId node_id) const { - return ContainsKey(all_nodes_, node_id); +template +bool Http2PriorityWriteScheduler::StreamRegistered( + StreamIdType stream_id) const { + return ContainsKey(all_stream_infos_, stream_id); } -template -bool SpdyPriorityTree::AddNode(NodeId node_id, - NodeId parent_id, - int weight, - bool exclusive) { - if (NodeExists(node_id) || weight < kMinWeight || weight > kMaxWeight) { - return false; +template +void Http2PriorityWriteScheduler::RegisterStream( + StreamIdType stream_id, + StreamIdType parent_id, + int weight, + bool exclusive) { + if (StreamRegistered(stream_id)) { + LOG(DFATAL) << "Stream " << stream_id << " already registered"; + return; } - Node* parent = FindNode(parent_id); + weight = ClampWeight(weight); + + StreamInfo* parent = FindStream(parent_id); if (parent == nullptr) { - return false; + LOG(DFATAL) << "Parent stream " << parent_id << " not registered"; + parent = root_stream_info_; } - Node* new_node = new Node; - new_node->id = node_id; - new_node->weight = weight; - new_node->parent = parent; - all_nodes_[node_id] = new_node; + + StreamInfo* new_stream_info = new StreamInfo; + new_stream_info->id = stream_id; + new_stream_info->weight = weight; + new_stream_info->parent = parent; + all_stream_infos_[stream_id] = new_stream_info; if (exclusive) { - // Move the parent's current children below the new node. + // Move the parent's current children below the new stream. using std::swap; - swap(new_node->children, parent->children); - new_node->total_child_weights = parent->total_child_weights; + swap(new_stream_info->children, parent->children); + new_stream_info->total_child_weights = parent->total_child_weights; // Update each child's parent. - for (Node* child : new_node->children) { - child->parent = new_node; + for (StreamInfo* child : new_stream_info->children) { + child->parent = new_stream_info; } // Clear parent's old child data. DCHECK(parent->children.empty()); parent->total_child_weights = 0; } - // Add new node to parent. - parent->children.push_back(new_node); + // Add new stream to parent. + parent->children.push_back(new_stream_info); parent->total_child_weights += weight; - return true; + + // 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->IsSchedulable()); } -template -bool SpdyPriorityTree::RemoveNode(NodeId node_id) { - if (node_id == kRootNodeId) { - return false; +template +void Http2PriorityWriteScheduler::UnregisterStream( + StreamIdType stream_id) { + if (stream_id == kHttp2RootStreamId) { + LOG(DFATAL) << "Cannot unregister root stream"; + return; } - // Remove the node from table. - typename NodeMap::iterator it = all_nodes_.find(node_id); - if (it == all_nodes_.end()) { - return false; + // Remove the stream from table. + typename StreamInfoMap::iterator it = all_stream_infos_.find(stream_id); + if (it == all_stream_infos_.end()) { + LOG(DFATAL) << "Stream " << stream_id << " not registered"; + return; + } + scoped_ptr stream_info(std::move(it->second)); + all_stream_infos_.erase(it); + // If scheduled, unschedule. + if (stream_info->scheduled) { + Unschedule(stream_info.get()); } - scoped_ptr node(it->second); - all_nodes_.erase(it); - Node* parent = node->parent; - // Remove the node from parent's child list. - Remove(&parent->children, node.get()); - parent->total_child_weights -= node->weight; + 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 node's children to the parent's child list. + // Move the stream's children to the parent's child list. // Update each child's parent and weight. - for (Node* child : node->children) { + for (StreamInfo* child : stream_info->children) { child->parent = parent; parent->children.push_back(child); - // Divide the removed node's weight among its children, rounding to the + // Divide the removed stream's weight among its children, rounding to the // nearest valid weight. - float float_weight = node->weight * static_cast(child->weight) / - static_cast(node->total_child_weights); + float float_weight = stream_info->weight * + static_cast(child->weight) / + static_cast(stream_info->total_child_weights); int new_weight = floor(float_weight + 0.5); if (new_weight == 0) { new_weight = 1; @@ -266,81 +345,111 @@ bool SpdyPriorityTree::RemoveNode(NodeId node_id) { child->weight = new_weight; parent->total_child_weights += child->weight; } - - return true; + UpdatePrioritiesUnder(parent); } -template -int SpdyPriorityTree::GetWeight(NodeId node_id) const { - const Node* node = FindNode(node_id); - return (node == nullptr) ? 0 : node->weight; +template +int Http2PriorityWriteScheduler::GetStreamWeight( + StreamIdType stream_id) const { + const StreamInfo* stream_info = FindStream(stream_id); + if (stream_info == nullptr) { + LOG(DFATAL) << "Stream " << stream_id << " not registered"; + return kHttp2MinStreamWeight; + } + return stream_info->weight; } -template -NodeId SpdyPriorityTree::GetParent(NodeId node_id) const { - const Node* node = FindNode(node_id); - // Root node has null parent. - return (node == nullptr || node->parent == nullptr) ? kRootNodeId - : node->parent->id; +template +StreamIdType Http2PriorityWriteScheduler::GetStreamParent( + StreamIdType stream_id) const { + const StreamInfo* stream_info = FindStream(stream_id); + if (stream_info == nullptr) { + LOG(DFATAL) << "Stream " << stream_id << " not registered"; + return kHttp2RootStreamId; + } + if (stream_info->parent == nullptr) { // root stream + return kHttp2RootStreamId; + } + return stream_info->parent->id; } -template -std::vector SpdyPriorityTree::GetChildren( - NodeId node_id) const { - std::vector child_vec; - const Node* node = FindNode(node_id); - if (node != nullptr) { - child_vec.reserve(node->children.size()); - for (Node* child : node->children) { +template +std::vector Http2PriorityWriteScheduler< + StreamIdType>::GetStreamChildren(StreamIdType stream_id) const { + std::vector child_vec; + const StreamInfo* stream_info = FindStream(stream_id); + if (stream_info == nullptr) { + LOG(DFATAL) << "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 -bool SpdyPriorityTree::SetWeight( - NodeId node_id, int weight) { - if (!NodeExists(node_id)) { - return false; +template +void Http2PriorityWriteScheduler::SetStreamWeight( + StreamIdType stream_id, + int weight) { + if (stream_id == kHttp2RootStreamId) { + LOG(DFATAL) << "Cannot set weight of root stream"; + return; } - if (weight < kMinWeight || weight > kMaxWeight) { - return false; + StreamInfo* stream_info = FindStream(stream_id); + if (stream_info == nullptr) { + LOG(DFATAL) << "Stream " << stream_id << " not registered"; + return; } - - Node* node = all_nodes_[node_id]; - if (node->parent != nullptr) { - node->parent->total_child_weights += (weight - node->weight); + weight = ClampWeight(weight); + if (weight == stream_info->weight) { + return; + } + if (stream_info->parent != nullptr) { + stream_info->parent->total_child_weights += (weight - stream_info->weight); } - node->weight = weight; + stream_info->weight = weight; - return true; + // Change in weight also affects sibling priorities. + UpdatePrioritiesUnder(stream_info->parent); } - -template -bool SpdyPriorityTree::SetParent( - NodeId node_id, NodeId parent_id, bool exclusive) { - if (node_id == kRootNodeId || node_id == parent_id) { - return false; +template +void Http2PriorityWriteScheduler::SetStreamParent( + StreamIdType stream_id, + StreamIdType parent_id, + bool exclusive) { + if (stream_id == kHttp2RootStreamId) { + LOG(DFATAL) << "Cannot set parent of root stream"; + return; } - Node* node = FindNode(node_id); - Node* new_parent = FindNode(parent_id); - if (node == nullptr || new_parent == nullptr) { - return false; + if (stream_id == parent_id) { + LOG(DFATAL) << "Cannot set stream to be its own parent"; + return; + } + StreamInfo* stream_info = FindStream(stream_id); + if (stream_info == nullptr) { + LOG(DFATAL) << "Stream " << stream_id << " not registered"; + return; + } + StreamInfo* new_parent = FindStream(parent_id); + if (new_parent == nullptr) { + LOG(DFATAL) << "Parent stream " << parent_id << " not registered"; + return; } - // If the new parent is already the node's parent, we're done. - if (node->parent == new_parent) { - return true; + // 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 node. - Node* last = new_parent->parent; + // of the stream. + StreamInfo* last = new_parent->parent; bool cycle_exists = false; while (last != nullptr) { - if (last == node) { + if (last == stream_info) { cycle_exists = true; break; } @@ -348,213 +457,280 @@ bool SpdyPriorityTree::SetParent( } if (cycle_exists) { - // The new parent moves to the level of the current node. - SetParent(parent_id, node->parent->id, false); + // The new parent moves to the level of the current stream. + SetStreamParent(parent_id, stream_info->parent->id, false); } - // Remove node from old parent's child list. - Node* old_parent = node->parent; - Remove(&old_parent->children, node); - old_parent->total_child_weights -= node->weight; + // 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 node. - for (Node* child : new_parent->children) { - child->parent = node; - node->children.push_back(child); + // 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); } - node->total_child_weights += new_parent->total_child_weights; + 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. - node->parent = new_parent; - new_parent->children.push_back(node); - new_parent->total_child_weights += node->weight; - return true; + stream_info->parent = new_parent; + new_parent->children.push_back(stream_info); + new_parent->total_child_weights += stream_info->weight; + UpdatePrioritiesUnder(new_parent); } -template -bool SpdyPriorityTree::SetBlocked(NodeId node_id, bool blocked) { - if (!NodeExists(node_id)) { - return false; +template +void Http2PriorityWriteScheduler::MarkStreamBlocked( + StreamIdType stream_id, + bool blocked) { + if (stream_id == kHttp2RootStreamId) { + LOG(DFATAL) << "Cannot mark root stream blocked or unblocked"; + return; + } + StreamInfo* stream_info = FindStream(stream_id); + if (stream_info == nullptr) { + LOG(DFATAL) << "Stream " << stream_id << " not registered"; + return; } + stream_info->blocked = blocked; + UpdateScheduling(stream_info); +} - Node* node = all_nodes_[node_id]; - node->blocked = blocked; - return true; +template +void Http2PriorityWriteScheduler::MarkStreamReady( + StreamIdType stream_id, + bool ready) { + if (stream_id == kHttp2RootStreamId) { + LOG(DFATAL) << "Cannot mark root stream ready or unready"; + return; + } + StreamInfo* stream_info = FindStream(stream_id); + if (stream_info == nullptr) { + LOG(DFATAL) << "Stream " << stream_id << " not registered"; + return; + } + stream_info->ready = ready; + UpdateScheduling(stream_info); } -template -bool SpdyPriorityTree::SetReady(NodeId node_id, bool ready) { - if (!NodeExists(node_id)) { - return false; +template +bool Http2PriorityWriteScheduler::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; + } } - Node* node = all_nodes_[node_id]; - node->ready = ready; - return true; + return false; } -template -bool SpdyPriorityTree::Remove(NodeVector* nodes, const Node* node) { - for (typename NodeVector::iterator it = nodes->begin(); it != nodes->end(); - ++it) { - if (*it == node) { - nodes->erase(it); +template +int Http2PriorityWriteScheduler::ClampWeight(int weight) { + if (weight < kHttp2MinStreamWeight) { + LOG(DFATAL) << "Invalid weight: " << weight; + return kHttp2MinStreamWeight; + } + if (weight > kHttp2MaxStreamWeight) { + LOG(DFATAL) << "Invalid weight: " << weight; + return kHttp2MaxStreamWeight; + } + return weight; +} + +template +bool Http2PriorityWriteScheduler::HasScheduledAncestor( + const StreamInfo& stream_info) { + for (const StreamInfo* parent = stream_info.parent; parent != nullptr; + parent = parent->parent) { + if (parent->scheduled) { return true; } } return false; } -template -void SpdyPriorityTree::PropagateNodeState(Node* node) { - // Reset total_writeable_child_weights to its maximum value. - node->total_writeable_child_weights = node->total_child_weights; - for (Node* child : node->children) { - PropagateNodeState(child); +template +const typename Http2PriorityWriteScheduler::StreamInfo* +Http2PriorityWriteScheduler::FindStream( + StreamIdType stream_id) const { + typename StreamInfoMap::const_iterator it = all_stream_infos_.find(stream_id); + return it == all_stream_infos_.end() ? nullptr : it->second; +} + +template +typename Http2PriorityWriteScheduler::StreamInfo* +Http2PriorityWriteScheduler::FindStream(StreamIdType stream_id) { + typename StreamInfoMap::iterator it = all_stream_infos_.find(stream_id); + return it == all_stream_infos_.end() ? nullptr : it->second; +} + +template +void Http2PriorityWriteScheduler::UpdatePrioritiesUnder( + StreamInfo* stream_info) { + for (StreamInfo* child : stream_info->children) { + child->priority = stream_info->priority * + (static_cast(child->weight) / + static_cast(stream_info->total_child_weights)); + if (child->scheduled) { + // 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); + } } - if (node->total_writeable_child_weights == 0 && - (node->blocked || !node->ready)) { - // Tell the parent that this entire subtree is unwriteable. - node->parent->total_writeable_child_weights -= node->weight; +} + +template +void Http2PriorityWriteScheduler::UpdateScheduling( + StreamInfo* stream_info) { + if (stream_info->IsSchedulable() != stream_info->scheduled) { + if (stream_info->scheduled) { + Unschedule(stream_info); + } else { + stream_info->ordinal = next_ordinal_++; + Schedule(stream_info); + } } } -template -const typename SpdyPriorityTree::Node* -SpdyPriorityTree::FindNode(NodeId node_id) const { - typename NodeMap::const_iterator it = all_nodes_.find(node_id); - return (it == all_nodes_.end() ? nullptr : it->second); +template +void Http2PriorityWriteScheduler::Schedule( + StreamInfo* stream_info) { + DCHECK(!stream_info->scheduled); + for (base::LinkNode* s = scheduling_queue_.head(); + s != scheduling_queue_.end(); s = s->next()) { + if (stream_info->SchedulesBefore(*s->value())) { + stream_info->InsertBefore(s); + stream_info->scheduled = true; + break; + } + } + if (!stream_info->scheduled) { + stream_info->InsertAfter(scheduling_queue_.tail()); + stream_info->scheduled = true; + } } -template -typename SpdyPriorityTree::Node* SpdyPriorityTree::FindNode( - NodeId node_id) { - typename NodeMap::const_iterator it = all_nodes_.find(node_id); - return (it == all_nodes_.end() ? nullptr : it->second); +template +void Http2PriorityWriteScheduler::Unschedule( + StreamInfo* stream_info) { + DCHECK(stream_info->scheduled); + stream_info->RemoveFromList(); + stream_info->scheduled = false; } -template -bool SpdyPriorityTree::HasChild(NodeId parent_id, - NodeId child_id) const { - const Node* parent = FindNode(parent_id); +template +bool Http2PriorityWriteScheduler::StreamHasChild( + StreamIdType parent_id, + StreamIdType child_id) const { + const StreamInfo* parent = FindStream(parent_id); if (parent == nullptr) { + LOG(DFATAL) << "Parent stream " << parent_id << " not registered"; + return false; + } + if (!StreamRegistered(child_id)) { + LOG(DFATAL) << "Child stream " << child_id << " not registered"; return false; } - auto found = - std::find_if(parent->children.begin(), parent->children.end(), - [child_id](Node* node) { return node->id == child_id; }); + auto found = std::find_if(parent->children.begin(), parent->children.end(), + [child_id](StreamInfo* stream_info) { + return stream_info->id == child_id; + }); return found != parent->children.end(); } -template -std::vector > -SpdyPriorityTree::GetPriorityList() { - PriorityList priority_list; - - // Update total_writeable_child_weights to reflect the current - // state of the tree. - PropagateNodeState(root_node_); - - std::deque queue; - DCHECK(root_node_->priority == 1.0); - // Start by examining our top-level nodes. - for (Node* child : root_node_->children) { - queue.push_back(child); - } - while (!queue.empty()) { - Node* current_node = queue.front(); - const Node* parent_node = current_node->parent; - if (current_node->blocked || !current_node->ready) { - if (current_node->total_writeable_child_weights > 0) { - // This node isn't writeable, but it has writeable children. - // Calculate the total fraction of resources we can allot - // to this subtree. - current_node->priority = parent_node->priority * - (static_cast(current_node->weight) / - static_cast(parent_node->total_writeable_child_weights)); - // Examine the children. - for (Node* child : current_node->children) { - queue.push_back(child); - } - } else { - // There's nothing to see in this subtree. - current_node->priority = 0; - } - } else { - // This node is writeable; calculate its priority. - current_node->priority = parent_node->priority * - (static_cast(current_node->weight) / - static_cast(parent_node->total_writeable_child_weights)); - // Add this node to the priority list. - priority_list.push_back( - PriorityNode(current_node->id, current_node->priority)); +template +bool Http2PriorityWriteScheduler::HasUsableStreams() const { + // Even though not every stream in scheduling queue is guaranteed to be + // usable (since children are occluded by parents), the presence of any + // streams guarantees at least one is usable. + return !scheduling_queue_.empty(); +} + +template +StreamIdType Http2PriorityWriteScheduler::PopNextUsableStream() { + for (base::LinkNode* s = scheduling_queue_.head(); + s != scheduling_queue_.end(); s = s->next()) { + StreamInfo* stream_info = s->value(); + if (!HasScheduledAncestor(*stream_info)) { + stream_info->ready = false; + Unschedule(stream_info); + return stream_info->id; } - // Remove this node from the queue. - queue.pop_front(); } - - // Sort the nodes in descending order of priority. - std::sort(priority_list.begin(), priority_list.end(), - NodePriorityComparator()); - - return priority_list; + LOG(DFATAL) << "No usable streams"; + return kHttp2RootStreamId; } -template -bool SpdyPriorityTree::ValidateInvariantsForTests() const { - int total_nodes = 0; - int nodes_visited = 0; - // Iterate through all nodes in the map. - for (const auto& kv : all_nodes_) { - ++total_nodes; - ++nodes_visited; - const Node& node = *kv.second; - // All nodes except the root should have a parent, and should appear in +template +bool Http2PriorityWriteScheduler::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; + const StreamInfo& stream_info = *kv.second; + // All streams except the root should have a parent, and should appear in // the children of that parent. - if (node.id != kRootNodeId && !HasChild(node.parent->id, node.id)) { - DLOG(INFO) << "Parent node " << node.parent->id - << " does not exist, or does not list node " << node.id - << " as its child."; + if (stream_info.id != kHttp2RootStreamId && + !StreamHasChild(stream_info.parent->id, stream_info.id)) { + 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 (!node.children.empty()) { + if (!stream_info.children.empty()) { int total_child_weights = 0; - // Iterate through the node's children. - for (Node* child : node.children) { - ++nodes_visited; - // Each node in the list should exist and should have this node + // 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 (!NodeExists(child->id) || node.id != GetParent(child->id)) { - DLOG(INFO) << "Child node " << child->id << " does not exist, " - << "or does not list " << node.id << " as its parent."; + if (!StreamRegistered(child->id) || + stream_info.id != GetStreamParent(child->id)) { + 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 != node.total_child_weights) { - DLOG(INFO) << "Child weight totals do not agree. For node " << node.id - << " total_child_weights has value " - << node.total_child_weights - << ", expected " << total_child_weights; + if (total_child_weights != stream_info.total_child_weights) { + 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_nodes reflects the total number of nodes the map contains. - if (total_nodes != num_nodes()) { - DLOG(INFO) << "Map contains incorrect number of nodes."; + // Make sure num_streams reflects the total number of streams the map + // contains. + if (total_streams != num_streams()) { + DLOG(INFO) << "Map contains incorrect number of streams."; return false; } - // Validate the validation function; we should have visited each node twice + // Validate the validation function; we should have visited each stream twice // (except for the root) - DCHECK(nodes_visited == 2*num_nodes() - 1); + DCHECK(streams_visited == 2 * num_streams() - 1); return true; } diff --git a/net/spdy/http2_write_scheduler_test.cc b/net/spdy/http2_write_scheduler_test.cc index c42646d0ae2446..f581d35326332e 100644 --- a/net/spdy/http2_write_scheduler_test.cc +++ b/net/spdy/http2_write_scheduler_test.cc @@ -4,112 +4,161 @@ #include "net/spdy/http2_write_scheduler.h" +#include "net/test/gtest_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { +using ::testing::AssertionFailure; +using ::testing::AssertionResult; +using ::testing::AssertionSuccess; using ::testing::ElementsAre; using ::testing::IsEmpty; using ::testing::UnorderedElementsAre; namespace test { -template -class SpdyPriorityTreePeer { +template +class Http2PriorityWriteSchedulerPeer { public: - explicit SpdyPriorityTreePeer(SpdyPriorityTree* tree) : tree_(tree) {} + explicit Http2PriorityWriteSchedulerPeer( + Http2PriorityWriteScheduler* scheduler) + : scheduler_(scheduler) {} - void PropagateNodeState(NodeId node_id) { - auto node = tree_->FindNode(node_id); - tree_->PropagateNodeState(node); - } - - int TotalChildWeights(NodeId node_id) const { - return tree_->FindNode(node_id)->total_child_weights; - } - - int TotalWriteableChildWeights(NodeId node_id) const { - return tree_->FindNode(node_id)->total_writeable_child_weights; + int TotalChildWeights(StreamIdType stream_id) const { + return scheduler_->FindStream(stream_id)->total_child_weights; } bool ValidateInvariants() const { - return tree_->ValidateInvariantsForTests(); + return scheduler_->ValidateInvariantsForTests(); } private: - SpdyPriorityTree* tree_; + Http2PriorityWriteScheduler* scheduler_; }; -class SpdyPriorityTreeTest : public ::testing::Test { +class Http2PriorityWriteSchedulerTest : public ::testing::Test { protected: - typedef uint32_t SpdyStreamId; - typedef std::pair PriorityNode; - typedef std::vector PriorityList; + using SpdyStreamId = uint32_t; - SpdyPriorityTreeTest() : peer(&tree) {} + Http2PriorityWriteSchedulerTest() : peer_(&scheduler_) {} - SpdyPriorityTree tree; - SpdyPriorityTreePeer peer; + Http2PriorityWriteScheduler scheduler_; + Http2PriorityWriteSchedulerPeer peer_; }; -TEST_F(SpdyPriorityTreeTest, AddAndRemoveNodes) { - EXPECT_EQ(1, tree.num_nodes()); - EXPECT_TRUE(tree.NodeExists(0)); - EXPECT_FALSE(tree.NodeExists(1)); - - EXPECT_TRUE(tree.AddNode(1, 0, 100, false)); - EXPECT_EQ(2, tree.num_nodes()); - ASSERT_TRUE(tree.NodeExists(1)); - EXPECT_EQ(100, tree.GetWeight(1)); - EXPECT_FALSE(tree.NodeExists(5)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(1)); - - EXPECT_TRUE(tree.AddNode(5, 0, 50, false)); - // Should not be able to add a node with an id that already exists. - EXPECT_FALSE(tree.AddNode(5, 1, 50, false)); - EXPECT_EQ(3, tree.num_nodes()); - EXPECT_TRUE(tree.NodeExists(1)); - ASSERT_TRUE(tree.NodeExists(5)); - EXPECT_EQ(50, tree.GetWeight(5)); - EXPECT_FALSE(tree.NodeExists(13)); - - EXPECT_TRUE(tree.AddNode(13, 5, 130, true)); - EXPECT_EQ(4, tree.num_nodes()); - EXPECT_TRUE(tree.NodeExists(1)); - EXPECT_TRUE(tree.NodeExists(5)); - ASSERT_TRUE(tree.NodeExists(13)); - EXPECT_EQ(130, tree.GetWeight(13)); - EXPECT_EQ(5u, tree.GetParent(13)); - - EXPECT_TRUE(tree.RemoveNode(5)); - // Cannot remove a node that has already been removed. - EXPECT_FALSE(tree.RemoveNode(5)); - EXPECT_EQ(3, tree.num_nodes()); - EXPECT_TRUE(tree.NodeExists(1)); - EXPECT_FALSE(tree.NodeExists(5)); - EXPECT_TRUE(tree.NodeExists(13)); - EXPECT_EQ(0u, tree.GetParent(13)); - - // The parent node 19 doesn't exist, so this should fail: - EXPECT_FALSE(tree.AddNode(7, 19, 70, false)); - // This should succeed, creating node 7: - EXPECT_TRUE(tree.AddNode(7, 13, 70, false)); - // Now node 7 already exists, so this should fail: - EXPECT_FALSE(tree.AddNode(7, 1, 70, false)); - // Try adding a second child to node 13: - EXPECT_TRUE(tree.AddNode(17, 13, 170, false)); +TEST_F(Http2PriorityWriteSchedulerTest, RegisterAndUnregisterStreams) { + EXPECT_EQ(1, scheduler_.num_streams()); + EXPECT_TRUE(scheduler_.StreamRegistered(0)); + EXPECT_FALSE(scheduler_.StreamRegistered(1)); + + scheduler_.RegisterStream(1, 0, 100, false); + EXPECT_EQ(2, scheduler_.num_streams()); + ASSERT_TRUE(scheduler_.StreamRegistered(1)); + EXPECT_EQ(100, scheduler_.GetStreamWeight(1)); + EXPECT_FALSE(scheduler_.StreamRegistered(5)); + EXPECT_THAT(scheduler_.GetStreamChildren(0), ElementsAre(1)); + + scheduler_.RegisterStream(5, 0, 50, false); + // Should not be able to add a stream with an id that already exists. + EXPECT_DFATAL(scheduler_.RegisterStream(5, 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_.GetStreamWeight(5)); + EXPECT_FALSE(scheduler_.StreamRegistered(13)); + + scheduler_.RegisterStream(13, 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_.GetStreamWeight(13)); + EXPECT_EQ(5u, scheduler_.GetStreamParent(13)); + + scheduler_.UnregisterStream(5); + // Cannot remove a stream that has already been removed. + EXPECT_DFATAL(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_.GetStreamParent(13)); + + // The parent stream 19 doesn't exist, so this should use 0 as parent stream: + EXPECT_DFATAL(scheduler_.RegisterStream(7, 19, 70, false), + "Parent stream 19 not registered"); + EXPECT_TRUE(scheduler_.StreamRegistered(7)); + EXPECT_EQ(0u, scheduler_.GetStreamParent(7)); + // Now stream 7 already exists, so this should fail: + EXPECT_DFATAL(scheduler_.RegisterStream(7, 1, 70, false), + "Stream 7 already registered"); + // Try adding a second child to stream 13: + scheduler_.RegisterStream(17, 13, 170, false); // TODO(birenroy): Add a separate test that verifies weight invariants when - // SetWeight is called. - EXPECT_TRUE(tree.SetWeight(17, 150)); - EXPECT_EQ(150, tree.GetWeight(17)); + // SetStreamWeight is called. + scheduler_.SetStreamWeight(17, 150); + EXPECT_EQ(150, scheduler_.GetStreamWeight(17)); + + ASSERT_TRUE(peer_.ValidateInvariants()); +} + +TEST_F(Http2PriorityWriteSchedulerTest, GetStreamWeight) { + EXPECT_DFATAL(EXPECT_EQ(kHttp2MinStreamWeight, scheduler_.GetStreamWeight(3)), + "Stream 3 not registered"); + scheduler_.RegisterStream(3, 0, 130, true); + EXPECT_EQ(130, scheduler_.GetStreamWeight(3)); + scheduler_.SetStreamWeight(3, 50); + EXPECT_EQ(50, scheduler_.GetStreamWeight(3)); + scheduler_.UnregisterStream(3); + EXPECT_DFATAL(EXPECT_EQ(kHttp2MinStreamWeight, scheduler_.GetStreamWeight(3)), + "Stream 3 not registered"); +} - ASSERT_TRUE(peer.ValidateInvariants()); +TEST_F(Http2PriorityWriteSchedulerTest, GetStreamParent) { + EXPECT_DFATAL(EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamParent(3)), + "Stream 3 not registered"); + scheduler_.RegisterStream(2, 0, 20, false); + scheduler_.RegisterStream(3, 2, 30, false); + EXPECT_EQ(2u, scheduler_.GetStreamParent(3)); + scheduler_.UnregisterStream(3); + EXPECT_DFATAL(EXPECT_EQ(kHttp2RootStreamId, scheduler_.GetStreamParent(3)), + "Stream 3 not registered"); +} + +TEST_F(Http2PriorityWriteSchedulerTest, GetStreamChildren) { + EXPECT_DFATAL(EXPECT_THAT(scheduler_.GetStreamChildren(7), IsEmpty()), + "Stream 7 not registered"); + scheduler_.RegisterStream(7, 0, 70, false); + EXPECT_THAT(scheduler_.GetStreamChildren(7), IsEmpty()); + scheduler_.RegisterStream(9, 7, 90, false); + scheduler_.RegisterStream(15, 7, 150, false); + EXPECT_THAT(scheduler_.GetStreamChildren(7), UnorderedElementsAre(9, 15)); + scheduler_.UnregisterStream(7); + EXPECT_DFATAL(EXPECT_THAT(scheduler_.GetStreamChildren(7), IsEmpty()), + "Stream 7 not registered"); +} + +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamWeight) { + EXPECT_DFATAL(scheduler_.SetStreamWeight(0, 10), + "Cannot set weight of root stream"); + EXPECT_DFATAL(scheduler_.SetStreamWeight(3, 10), "Stream 3 not registered"); + scheduler_.RegisterStream(3, 0, 10, false); + scheduler_.SetStreamWeight(3, 20); + EXPECT_EQ(20, scheduler_.GetStreamWeight(3)); + EXPECT_DFATAL(scheduler_.SetStreamWeight(3, 500), "Invalid weight: 500"); + EXPECT_EQ(kHttp2MaxStreamWeight, scheduler_.GetStreamWeight(3)); + EXPECT_DFATAL(scheduler_.SetStreamWeight(3, 0), "Invalid weight: 0"); + EXPECT_EQ(kHttp2MinStreamWeight, scheduler_.GetStreamWeight(3)); + scheduler_.UnregisterStream(3); + EXPECT_DFATAL(scheduler_.SetStreamWeight(3, 10), "Stream 3 not registered"); } // Basic case of reparenting a subtree. -TEST_F(SpdyPriorityTreeTest, SetParentBasicNonExclusive) { +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentBasicNonExclusive) { /* Tree: 0 / \ @@ -117,22 +166,22 @@ TEST_F(SpdyPriorityTreeTest, SetParentBasicNonExclusive) { / \ 3 4 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 0, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 1, 100, false); - EXPECT_TRUE(tree.SetParent(1, 2, false)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(2)); - EXPECT_THAT(tree.GetChildren(1), UnorderedElementsAre(3, 4)); - EXPECT_THAT(tree.GetChildren(2), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 0, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 1, 100, false); + scheduler_.SetStreamParent(1, 2, 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(SpdyPriorityTreeTest, SetParentBasicExclusive) { +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentBasicExclusive) { /* Tree: 0 / \ @@ -140,38 +189,41 @@ TEST_F(SpdyPriorityTreeTest, SetParentBasicExclusive) { / \ 3 4 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 0, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 1, 100, false); - EXPECT_TRUE(tree.SetParent(1, 2, true)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(2)); - EXPECT_THAT(tree.GetChildren(1), UnorderedElementsAre(3, 4)); - EXPECT_THAT(tree.GetChildren(2), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 0, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 1, 100, false); + scheduler_.SetStreamParent(1, 2, 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 node, or set the parent to a -// nonexistent node. -TEST_F(SpdyPriorityTreeTest, SetParentNonexistent) { - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 0, 100, false); - bool test_bool_values[] = {true, false}; - for (bool exclusive : test_bool_values) { - EXPECT_FALSE(tree.SetParent(1, 3, exclusive)); - EXPECT_FALSE(tree.SetParent(4, 2, exclusive)); - EXPECT_FALSE(tree.SetParent(3, 4, exclusive)); - EXPECT_THAT(tree.GetChildren(0), UnorderedElementsAre(1, 2)); - EXPECT_THAT(tree.GetChildren(1), IsEmpty()); - EXPECT_THAT(tree.GetChildren(2), IsEmpty()); +// We can't set the parent of a nonexistent stream, or set the parent to a +// nonexistent stream. +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentNonexistent) { + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 0, 100, false); + for (bool exclusive : {true, false}) { + EXPECT_DFATAL(scheduler_.SetStreamParent(1, 3, exclusive), + "Parent stream 3 not registered"); + EXPECT_DFATAL(scheduler_.SetStreamParent(4, 2, exclusive), + "Stream 4 not registered"); + EXPECT_DFATAL(scheduler_.SetStreamParent(3, 4, exclusive), + "Stream 3 not registered"); + EXPECT_THAT(scheduler_.GetStreamChildren(0), UnorderedElementsAre(1, 2)); + EXPECT_THAT(scheduler_.GetStreamChildren(1), IsEmpty()); + EXPECT_THAT(scheduler_.GetStreamChildren(2), IsEmpty()); } - ASSERT_TRUE(peer.ValidateInvariants()); + ASSERT_TRUE(peer_.ValidateInvariants()); } -// We should be able to add multiple children to nodes. -TEST_F(SpdyPriorityTreeTest, SetParentMultipleChildrenNonExclusive) { +// We should be able to add multiple children to streams. +TEST_F(Http2PriorityWriteSchedulerTest, + SetStreamParentMultipleChildrenNonExclusive) { /* Tree: 0 / \ @@ -179,22 +231,23 @@ TEST_F(SpdyPriorityTreeTest, SetParentMultipleChildrenNonExclusive) { / \ \ 3 4 5 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 0, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 1, 100, false); - tree.AddNode(5, 2, 100, false); - EXPECT_TRUE(tree.SetParent(2, 1, false)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(1), UnorderedElementsAre(2, 3, 4)); - EXPECT_THAT(tree.GetChildren(2), ElementsAre(5)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), IsEmpty()); - EXPECT_THAT(tree.GetChildren(5), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 0, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 1, 100, false); + scheduler_.RegisterStream(5, 2, 100, false); + scheduler_.SetStreamParent(2, 1, 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(SpdyPriorityTreeTest, SetParentMultipleChildrenExclusive) { +TEST_F(Http2PriorityWriteSchedulerTest, + SetStreamParentMultipleChildrenExclusive) { /* Tree: 0 / \ @@ -202,22 +255,22 @@ TEST_F(SpdyPriorityTreeTest, SetParentMultipleChildrenExclusive) { / \ \ 3 4 5 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 0, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 1, 100, false); - tree.AddNode(5, 2, 100, false); - EXPECT_TRUE(tree.SetParent(2, 1, true)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(1), ElementsAre(2)); - EXPECT_THAT(tree.GetChildren(2), UnorderedElementsAre(3, 4, 5)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), IsEmpty()); - EXPECT_THAT(tree.GetChildren(5), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 0, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 1, 100, false); + scheduler_.RegisterStream(5, 2, 100, false); + scheduler_.SetStreamParent(2, 1, 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(SpdyPriorityTreeTest, SetParentToChildNonExclusive) { +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentToChildNonExclusive) { /* Tree: 0 | @@ -227,20 +280,20 @@ TEST_F(SpdyPriorityTreeTest, SetParentToChildNonExclusive) { | 4 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 1, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 2, 100, false); - EXPECT_TRUE(tree.SetParent(1, 2, false)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(2)); - EXPECT_THAT(tree.GetChildren(1), ElementsAre(3)); - EXPECT_THAT(tree.GetChildren(2), UnorderedElementsAre(1, 4)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 1, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 2, 100, false); + scheduler_.SetStreamParent(1, 2, 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(SpdyPriorityTreeTest, SetParentToChildExclusive) { +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentToChildExclusive) { /* Tree: 0 | @@ -250,20 +303,21 @@ TEST_F(SpdyPriorityTreeTest, SetParentToChildExclusive) { | 4 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 1, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 2, 100, false); - EXPECT_TRUE(tree.SetParent(1, 2, true)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(2)); - EXPECT_THAT(tree.GetChildren(1), UnorderedElementsAre(3, 4)); - EXPECT_THAT(tree.GetChildren(2), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 1, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 2, 100, false); + scheduler_.SetStreamParent(1, 2, 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(SpdyPriorityTreeTest, SetParentToGrandchildNonExclusive) { +TEST_F(Http2PriorityWriteSchedulerTest, + SetStreamParentToGrandchildNonExclusive) { /* Tree: 0 | @@ -275,24 +329,24 @@ TEST_F(SpdyPriorityTreeTest, SetParentToGrandchildNonExclusive) { | 6 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 1, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 2, 100, false); - tree.AddNode(5, 2, 100, false); - tree.AddNode(6, 4, 100, false); - EXPECT_TRUE(tree.SetParent(1, 4, false)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(4)); - EXPECT_THAT(tree.GetChildren(1), UnorderedElementsAre(2, 3)); - EXPECT_THAT(tree.GetChildren(2), ElementsAre(5)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), UnorderedElementsAre(1, 6)); - EXPECT_THAT(tree.GetChildren(5), IsEmpty()); - EXPECT_THAT(tree.GetChildren(6), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 1, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 2, 100, false); + scheduler_.RegisterStream(5, 2, 100, false); + scheduler_.RegisterStream(6, 4, 100, false); + scheduler_.SetStreamParent(1, 4, 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(SpdyPriorityTreeTest, SetParentToGrandchildExclusive) { +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentToGrandchildExclusive) { /* Tree: 0 | @@ -304,48 +358,63 @@ TEST_F(SpdyPriorityTreeTest, SetParentToGrandchildExclusive) { | 6 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 1, 100, false); - tree.AddNode(3, 1, 100, false); - tree.AddNode(4, 2, 100, false); - tree.AddNode(5, 2, 100, false); - tree.AddNode(6, 4, 100, false); - EXPECT_TRUE(tree.SetParent(1, 4, true)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(4)); - EXPECT_THAT(tree.GetChildren(1), UnorderedElementsAre(2, 3, 6)); - EXPECT_THAT(tree.GetChildren(2), ElementsAre(5)); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); - EXPECT_THAT(tree.GetChildren(4), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(5), IsEmpty()); - EXPECT_THAT(tree.GetChildren(6), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 1, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + scheduler_.RegisterStream(4, 2, 100, false); + scheduler_.RegisterStream(5, 2, 100, false); + scheduler_.RegisterStream(6, 4, 100, false); + scheduler_.SetStreamParent(1, 4, 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(SpdyPriorityTreeTest, SetParentToParent) { - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 1, 100, false); - tree.AddNode(3, 1, 100, false); - bool test_bool_values[] = {true, false}; - for (bool exclusive : test_bool_values) { - EXPECT_TRUE(tree.SetParent(2, 1, exclusive)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(1), UnorderedElementsAre(2, 3)); - EXPECT_THAT(tree.GetChildren(2), IsEmpty()); - EXPECT_THAT(tree.GetChildren(3), IsEmpty()); +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentToParent) { + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 1, 100, false); + scheduler_.RegisterStream(3, 1, 100, false); + for (bool exclusive : {true, false}) { + scheduler_.SetStreamParent(2, 1, 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()); + ASSERT_TRUE(peer_.ValidateInvariants()); } -TEST_F(SpdyPriorityTreeTest, SetParentToSelf) { - tree.AddNode(1, 0, 100, false); - EXPECT_FALSE(tree.SetParent(1, 1, false)); - EXPECT_FALSE(tree.SetParent(1, 1, true)); - EXPECT_THAT(tree.GetChildren(0), ElementsAre(1)); - EXPECT_THAT(tree.GetChildren(1), IsEmpty()); - ASSERT_TRUE(peer.ValidateInvariants()); +TEST_F(Http2PriorityWriteSchedulerTest, SetStreamParentToSelf) { + scheduler_.RegisterStream(1, 0, 100, false); + EXPECT_DFATAL(scheduler_.SetStreamParent(1, 1, false), + "Cannot set stream to be its own parent"); + EXPECT_DFATAL(scheduler_.SetStreamParent(1, 1, 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(SpdyPriorityTreeTest, BlockAndUnblock) { +TEST_F(Http2PriorityWriteSchedulerTest, StreamHasChild) { + scheduler_.RegisterStream(1, 0, 10, false); + scheduler_.RegisterStream(2, 1, 20, false); + scheduler_.RegisterStream(3, 1, 30, false); + EXPECT_DFATAL(EXPECT_FALSE(scheduler_.StreamHasChild(4, 1)), + "Parent stream 4 not registered"); + EXPECT_DFATAL(EXPECT_FALSE(scheduler_.StreamHasChild(3, 7)), + "Child stream 7 not registered"); + EXPECT_FALSE(scheduler_.StreamHasChild(3, 1)); + EXPECT_TRUE(scheduler_.StreamHasChild(1, 3)); + EXPECT_TRUE(scheduler_.StreamHasChild(1, 2)); + ASSERT_TRUE(peer_.ValidateInvariants()); +} + +TEST_F(Http2PriorityWriteSchedulerTest, BlockAndUnblock) { /* Create the tree. 0 @@ -360,218 +429,73 @@ TEST_F(SpdyPriorityTreeTest, BlockAndUnblock) { 15 16 */ - tree.AddNode(1, 0, 100, false); - tree.AddNode(2, 0, 100, false); - tree.AddNode(3, 0, 100, false); - tree.AddNode(4, 1, 100, false); - tree.AddNode(5, 1, 100, false); - tree.AddNode(8, 4, 100, false); - tree.AddNode(9, 4, 100, false); - tree.AddNode(10, 5, 100, false); - tree.AddNode(11, 5, 100, false); - tree.AddNode(15, 8, 100, false); - tree.AddNode(16, 8, 100, false); - tree.AddNode(12, 2, 100, false); - tree.AddNode(6, 2, 100, true); - tree.AddNode(7, 0, 100, false); - tree.AddNode(13, 7, 100, true); - tree.AddNode(14, 7, 100, false); - tree.SetParent(7, 3, false); - EXPECT_EQ(0u, tree.GetParent(1)); - EXPECT_EQ(0u, tree.GetParent(2)); - EXPECT_EQ(0u, tree.GetParent(3)); - EXPECT_EQ(1u, tree.GetParent(4)); - EXPECT_EQ(1u, tree.GetParent(5)); - EXPECT_EQ(2u, tree.GetParent(6)); - EXPECT_EQ(3u, tree.GetParent(7)); - EXPECT_EQ(4u, tree.GetParent(8)); - EXPECT_EQ(4u, tree.GetParent(9)); - EXPECT_EQ(5u, tree.GetParent(10)); - EXPECT_EQ(5u, tree.GetParent(11)); - EXPECT_EQ(6u, tree.GetParent(12)); - EXPECT_EQ(7u, tree.GetParent(13)); - EXPECT_EQ(7u, tree.GetParent(14)); - EXPECT_EQ(8u, tree.GetParent(15)); - EXPECT_EQ(8u, tree.GetParent(16)); - ASSERT_TRUE(peer.ValidateInvariants()); - - EXPECT_EQ(peer.TotalChildWeights(0), - tree.GetWeight(1) + tree.GetWeight(2) + tree.GetWeight(3)); - EXPECT_EQ(peer.TotalChildWeights(3), tree.GetWeight(7)); - EXPECT_EQ(peer.TotalChildWeights(7), tree.GetWeight(13) + tree.GetWeight(14)); - EXPECT_EQ(peer.TotalChildWeights(13), 0); - EXPECT_EQ(peer.TotalChildWeights(14), 0); - - // Set all nodes ready to write. - EXPECT_TRUE(tree.SetReady(1, true)); - EXPECT_TRUE(tree.SetReady(2, true)); - EXPECT_TRUE(tree.SetReady(3, true)); - EXPECT_TRUE(tree.SetReady(4, true)); - EXPECT_TRUE(tree.SetReady(5, true)); - EXPECT_TRUE(tree.SetReady(6, true)); - EXPECT_TRUE(tree.SetReady(7, true)); - EXPECT_TRUE(tree.SetReady(8, true)); - EXPECT_TRUE(tree.SetReady(9, true)); - EXPECT_TRUE(tree.SetReady(10, true)); - EXPECT_TRUE(tree.SetReady(11, true)); - EXPECT_TRUE(tree.SetReady(12, true)); - EXPECT_TRUE(tree.SetReady(13, true)); - EXPECT_TRUE(tree.SetReady(14, true)); - EXPECT_TRUE(tree.SetReady(15, true)); - EXPECT_TRUE(tree.SetReady(16, true)); - - // Number of readable child weights should not change because - // 7 has unblocked children. - tree.SetBlocked(7, true); - peer.PropagateNodeState(kRootNodeId); - EXPECT_EQ(peer.TotalChildWeights(3), peer.TotalWriteableChildWeights(3)); - - // Readable children for 7 should decrement. - // Number of readable child weights for 3 still should not change. - tree.SetBlocked(13, true); - peer.PropagateNodeState(kRootNodeId); - EXPECT_EQ(peer.TotalChildWeights(3), peer.TotalWriteableChildWeights(3)); - EXPECT_EQ(tree.GetWeight(14), peer.TotalWriteableChildWeights(7)); - - // Once 14 becomes blocked, readable children for 7 and 3 should both be - // decremented. Total readable weights at the root should still be the same - // because 3 is still writeable. - tree.SetBlocked(14, true); - peer.PropagateNodeState(kRootNodeId); - EXPECT_EQ(0, peer.TotalWriteableChildWeights(3)); - EXPECT_EQ(0, peer.TotalWriteableChildWeights(7)); - EXPECT_EQ(peer.TotalChildWeights(0), - tree.GetWeight(1) + tree.GetWeight(2) + tree.GetWeight(3)); - - // And now the root should be decremented as well. - tree.SetBlocked(3, true); - peer.PropagateNodeState(kRootNodeId); - EXPECT_EQ(tree.GetWeight(1) + tree.GetWeight(2), - peer.TotalWriteableChildWeights(0)); - - // Unblocking 7 should propagate all the way up to the root. - tree.SetBlocked(7, false); - peer.PropagateNodeState(kRootNodeId); - EXPECT_EQ(peer.TotalWriteableChildWeights(0), - tree.GetWeight(1) + tree.GetWeight(2) + tree.GetWeight(3)); - EXPECT_EQ(peer.TotalWriteableChildWeights(3), tree.GetWeight(7)); - EXPECT_EQ(0, peer.TotalWriteableChildWeights(7)); - - // Ditto for reblocking 7. - tree.SetBlocked(7, true); - peer.PropagateNodeState(kRootNodeId); - EXPECT_EQ(peer.TotalWriteableChildWeights(0), - tree.GetWeight(1) + tree.GetWeight(2)); - EXPECT_EQ(0, peer.TotalWriteableChildWeights(3)); - EXPECT_EQ(0, peer.TotalWriteableChildWeights(7)); - ASSERT_TRUE(peer.ValidateInvariants()); + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 0, 100, false); + scheduler_.RegisterStream(3, 0, 100, false); + scheduler_.RegisterStream(4, 1, 100, false); + scheduler_.RegisterStream(5, 1, 100, false); + scheduler_.RegisterStream(8, 4, 100, false); + scheduler_.RegisterStream(9, 4, 100, false); + scheduler_.RegisterStream(10, 5, 100, false); + scheduler_.RegisterStream(11, 5, 100, false); + scheduler_.RegisterStream(15, 8, 100, false); + scheduler_.RegisterStream(16, 8, 100, false); + scheduler_.RegisterStream(12, 2, 100, false); + scheduler_.RegisterStream(6, 2, 100, true); + scheduler_.RegisterStream(7, 0, 100, false); + scheduler_.RegisterStream(13, 7, 100, true); + scheduler_.RegisterStream(14, 7, 100, false); + scheduler_.SetStreamParent(7, 3, false); + EXPECT_EQ(0u, scheduler_.GetStreamParent(1)); + EXPECT_EQ(0u, scheduler_.GetStreamParent(2)); + EXPECT_EQ(0u, scheduler_.GetStreamParent(3)); + EXPECT_EQ(1u, scheduler_.GetStreamParent(4)); + EXPECT_EQ(1u, scheduler_.GetStreamParent(5)); + EXPECT_EQ(2u, scheduler_.GetStreamParent(6)); + EXPECT_EQ(3u, scheduler_.GetStreamParent(7)); + EXPECT_EQ(4u, scheduler_.GetStreamParent(8)); + EXPECT_EQ(4u, scheduler_.GetStreamParent(9)); + EXPECT_EQ(5u, scheduler_.GetStreamParent(10)); + EXPECT_EQ(5u, scheduler_.GetStreamParent(11)); + EXPECT_EQ(6u, scheduler_.GetStreamParent(12)); + EXPECT_EQ(7u, scheduler_.GetStreamParent(13)); + EXPECT_EQ(7u, scheduler_.GetStreamParent(14)); + EXPECT_EQ(8u, scheduler_.GetStreamParent(15)); + EXPECT_EQ(8u, scheduler_.GetStreamParent(16)); + ASSERT_TRUE(peer_.ValidateInvariants()); + + EXPECT_EQ(peer_.TotalChildWeights(0), scheduler_.GetStreamWeight(1) + + scheduler_.GetStreamWeight(2) + + scheduler_.GetStreamWeight(3)); + EXPECT_EQ(peer_.TotalChildWeights(3), scheduler_.GetStreamWeight(7)); + EXPECT_EQ(peer_.TotalChildWeights(7), + scheduler_.GetStreamWeight(13) + scheduler_.GetStreamWeight(14)); + EXPECT_EQ(peer_.TotalChildWeights(13), 0); + EXPECT_EQ(peer_.TotalChildWeights(14), 0); + + ASSERT_TRUE(peer_.ValidateInvariants()); } -TEST_F(SpdyPriorityTreeTest, GetPriorityList) { - PriorityList expected_list; - PriorityList priority_list; - - /* Create the tree. - - 0 - /|\ - 1 2 3 - /| |\ - 4 5 6 7 - / - 8 - - */ - tree.AddNode(1, 0, 10, false); - tree.AddNode(2, 0, 20, false); - tree.AddNode(3, 0, 30, false); - tree.AddNode(4, 1, 10, false); - tree.AddNode(5, 1, 90, false); - tree.AddNode(6, 2, 10, false); - tree.AddNode(7, 2, 10, false); - tree.AddNode(8, 4, 256, false); - - // Set all nodes ready to write. - EXPECT_TRUE(tree.SetReady(1, true)); - EXPECT_TRUE(tree.SetReady(2, true)); - EXPECT_TRUE(tree.SetReady(3, true)); - EXPECT_TRUE(tree.SetReady(4, true)); - EXPECT_TRUE(tree.SetReady(5, true)); - EXPECT_TRUE(tree.SetReady(6, true)); - EXPECT_TRUE(tree.SetReady(7, true)); - EXPECT_TRUE(tree.SetReady(8, true)); - - expected_list.push_back(PriorityNode(3, 1.0/2.0)); - expected_list.push_back(PriorityNode(2, 1.0/3.0)); - expected_list.push_back(PriorityNode(1, 1.0/6.0)); - priority_list = tree.GetPriorityList(); - EXPECT_EQ(expected_list, priority_list); - - // Check that the list updates as expected when a node gets blocked. - EXPECT_TRUE(tree.SetReady(1, false)); - expected_list.clear(); - expected_list.push_back(PriorityNode(3, 1.0/2.0)); - expected_list.push_back(PriorityNode(2, 1.0/3.0)); - expected_list.push_back(PriorityNode(5, 0.9*1.0/6.0)); - expected_list.push_back(PriorityNode(4, 0.1*1.0/6.0)); - priority_list = tree.GetPriorityList(); - EXPECT_EQ(expected_list, priority_list); - - // Block multiple levels of nodes. - EXPECT_TRUE(tree.SetReady(4, false)); - EXPECT_TRUE(tree.SetReady(5, false)); - expected_list.clear(); - expected_list.push_back(PriorityNode(3, 1.0/2.0)); - expected_list.push_back(PriorityNode(2, 1.0/3.0)); - expected_list.push_back(PriorityNode(8, 1.0/6.0)); - priority_list = tree.GetPriorityList(); - EXPECT_EQ(expected_list, priority_list); - - // Remove a node from the tree to make sure priorities - // get redistributed accordingly. - EXPECT_TRUE(tree.RemoveNode(1)); - expected_list.clear(); - expected_list.push_back(PriorityNode(3, 30.0/51.0)); - expected_list.push_back(PriorityNode(2, 20.0/51.0)); - expected_list.push_back(PriorityNode(8, 1.0/51.0)); - priority_list = tree.GetPriorityList(); - EXPECT_EQ(expected_list, priority_list); - - // Block an entire subtree. - EXPECT_TRUE(tree.SetReady(8, false)); - expected_list.clear(); - expected_list.push_back(PriorityNode(3, 0.6)); - expected_list.push_back(PriorityNode(2, 0.4)); - priority_list = tree.GetPriorityList(); - EXPECT_EQ(expected_list, priority_list); - - // Unblock previously blocked nodes. - EXPECT_TRUE(tree.SetReady(4, true)); - EXPECT_TRUE(tree.SetReady(5, true)); - expected_list.clear(); - expected_list.push_back(PriorityNode(3, 1.0/2.0)); - expected_list.push_back(PriorityNode(2, 1.0/3.0)); - expected_list.push_back(PriorityNode(5, 9.0/60.0)); - expected_list.push_back(PriorityNode(4, 1.0/60.0)); - priority_list = tree.GetPriorityList(); - EXPECT_EQ(expected_list, priority_list); - - // Blocked nodes in multiple subtrees. - EXPECT_TRUE(tree.SetReady(2, false)); - EXPECT_TRUE(tree.SetReady(6, false)); - EXPECT_TRUE(tree.SetReady(7, false)); - expected_list.clear(); - expected_list.push_back(PriorityNode(3, 3.0/4.0)); - expected_list.push_back(PriorityNode(5, 9.0/40.0)); - expected_list.push_back(PriorityNode(4, 1.0/40.0)); - priority_list = tree.GetPriorityList(); - EXPECT_EQ(expected_list, priority_list); +TEST_F(Http2PriorityWriteSchedulerTest, HasUsableStreams) { + EXPECT_FALSE(scheduler_.HasUsableStreams()); + scheduler_.RegisterStream(1, 0, 10, false); + EXPECT_FALSE(scheduler_.HasUsableStreams()); + scheduler_.MarkStreamReady(1, true); + EXPECT_TRUE(scheduler_.HasUsableStreams()); + scheduler_.MarkStreamBlocked(1, true); + EXPECT_FALSE(scheduler_.HasUsableStreams()); + scheduler_.MarkStreamReady(1, false); + EXPECT_FALSE(scheduler_.HasUsableStreams()); + scheduler_.MarkStreamBlocked(1, false); + EXPECT_FALSE(scheduler_.HasUsableStreams()); + scheduler_.MarkStreamReady(1, true); + EXPECT_TRUE(scheduler_.HasUsableStreams()); + scheduler_.UnregisterStream(1); + EXPECT_FALSE(scheduler_.HasUsableStreams()); + ASSERT_TRUE(peer_.ValidateInvariants()); } -TEST_F(SpdyPriorityTreeTest, CalculateRoundedWeights) { - PriorityList expected_list; - PriorityList priority_list; - +TEST_F(Http2PriorityWriteSchedulerTest, CalculateRoundedWeights) { /* Create the tree. 0 @@ -580,28 +504,172 @@ TEST_F(SpdyPriorityTreeTest, CalculateRoundedWeights) { /| |\ |\ 8 3 4 5 6 7 */ - tree.AddNode(3, 0, 100, false); - tree.AddNode(4, 0, 100, false); - tree.AddNode(5, 0, 100, false); - tree.AddNode(1, 0, 10, true); - tree.AddNode(2, 0, 5, false); - tree.AddNode(6, 2, 1, false); - tree.AddNode(7, 2, 1, false); - tree.AddNode(8, 1, 1, false); - - // Remove higher-level nodes. - tree.RemoveNode(1); - tree.RemoveNode(2); + scheduler_.RegisterStream(3, 0, 100, false); + scheduler_.RegisterStream(4, 0, 100, false); + scheduler_.RegisterStream(5, 0, 100, false); + scheduler_.RegisterStream(1, 0, 10, true); + scheduler_.RegisterStream(2, 0, 5, false); + scheduler_.RegisterStream(6, 2, 1, false); + scheduler_.RegisterStream(7, 2, 1, false); + scheduler_.RegisterStream(8, 1, 1, false); + + // Remove higher-level streams. + scheduler_.UnregisterStream(1); + scheduler_.UnregisterStream(2); // 3.3 rounded down = 3. - EXPECT_EQ(3, tree.GetWeight(3)); - EXPECT_EQ(3, tree.GetWeight(4)); - EXPECT_EQ(3, tree.GetWeight(5)); + EXPECT_EQ(3, scheduler_.GetStreamWeight(3)); + EXPECT_EQ(3, scheduler_.GetStreamWeight(4)); + EXPECT_EQ(3, scheduler_.GetStreamWeight(5)); // 2.5 rounded up = 3. - EXPECT_EQ(3, tree.GetWeight(6)); - EXPECT_EQ(3, tree.GetWeight(7)); + EXPECT_EQ(3, scheduler_.GetStreamWeight(6)); + EXPECT_EQ(3, scheduler_.GetStreamWeight(7)); // 0 is not a valid weight, so round up to 1. - EXPECT_EQ(1, tree.GetWeight(8)); + EXPECT_EQ(1, scheduler_.GetStreamWeight(8)); + ASSERT_TRUE(peer_.ValidateInvariants()); } + +class PopNextUsableStreamTest : public Http2PriorityWriteSchedulerTest { + protected: + void SetUp() override { + /* Create the tree. + + 0 + /|\ + 1 2 3 + /| |\ + 4 5 6 7 + / + 8 + + */ + scheduler_.RegisterStream(1, 0, 100, false); + scheduler_.RegisterStream(2, 0, 100, false); + scheduler_.RegisterStream(3, 0, 100, false); + scheduler_.RegisterStream(4, 1, 100, false); + scheduler_.RegisterStream(5, 1, 100, false); + scheduler_.RegisterStream(6, 2, 100, false); + scheduler_.RegisterStream(7, 2, 100, false); + scheduler_.RegisterStream(8, 4, 100, false); + + // Set all nodes ready to write. + for (SpdyStreamId id = 1; id <= 8; ++id) { + scheduler_.MarkStreamReady(id, true); + } + } + + AssertionResult PopNextReturnsCycle( + std::initializer_list 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_.PopNextUsableStream(); + scheduler_.MarkStreamReady(next_id, true); + 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(PopNextUsableStreamTest, NoneBlocked) { + EXPECT_TRUE(PopNextReturnsCycle({1, 2, 3})); +} + +// When a parent stream is blocked, its children should be scheduled, if +// priorities allow. +TEST_F(PopNextUsableStreamTest, SingleStreamBlocked) { + scheduler_.MarkStreamReady(1, false); + + // 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_.SetStreamWeight(1, 200); + EXPECT_TRUE(PopNextReturnsCycle({4, 5, 2, 3})); +} + +// Block multiple levels of streams. +TEST_F(PopNextUsableStreamTest, MultiLevelBlocked) { + for (SpdyStreamId stream_id : {1, 4, 5}) { + scheduler_.MarkStreamReady(stream_id, false); + } + // 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_.SetStreamWeight(1, 200); + EXPECT_TRUE(PopNextReturnsCycle({8, 2, 3})); +} + +// A removed stream shouldn't be scheduled. +TEST_F(PopNextUsableStreamTest, 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_.SetStreamWeight(4, 100); + scheduler_.SetStreamWeight(5, 100); + EXPECT_TRUE(PopNextReturnsCycle({4, 5, 2, 3})); +} + +// Block an entire subtree. +TEST_F(PopNextUsableStreamTest, SubtreeBlocked) { + for (SpdyStreamId stream_id : {1, 4, 5, 8}) { + scheduler_.MarkStreamReady(stream_id, false); + } + EXPECT_TRUE(PopNextReturnsCycle({2, 3})); +} + +// If all parent streams are blocked, children should be returned. +TEST_F(PopNextUsableStreamTest, ParentsBlocked) { + for (SpdyStreamId stream_id : {1, 2, 3}) { + scheduler_.MarkStreamReady(stream_id, false); + } + EXPECT_TRUE(PopNextReturnsCycle({4, 5, 6, 7})); +} + +// Unblocking streams should make them schedulable. +TEST_F(PopNextUsableStreamTest, BlockAndUnblock) { + EXPECT_TRUE(PopNextReturnsCycle({1, 2, 3})); + scheduler_.MarkStreamReady(2, false); + EXPECT_TRUE(PopNextReturnsCycle({1, 3})); + scheduler_.MarkStreamReady(2, true); + // Cycle order permuted since 2 effectively appended at tail. + EXPECT_TRUE(PopNextReturnsCycle({1, 3, 2})); +} + +// Block nodes in multiple subtrees. +TEST_F(PopNextUsableStreamTest, ScatteredBlocked) { + for (SpdyStreamId stream_id : {1, 2, 6, 7}) { + scheduler_.MarkStreamReady(stream_id, false); + } + // 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_.SetStreamWeight(1, 200); + EXPECT_TRUE(PopNextReturnsCycle({4, 5, 3})); + + // When 4 is blocked, its child 8 should take its place, since it has same + // priority. + scheduler_.MarkStreamReady(4, false); + EXPECT_TRUE(PopNextReturnsCycle({8, 5, 3})); +} + } // namespace test -} // namespace gfe_spdy +} // namespace net