Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(492)

Unified Diff: net/spdy/http2_write_scheduler.h

Issue 1660283002: Rename and modify SpdyPriorityTree. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase on https://crrev.com/1668773003. Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | net/spdy/http2_write_scheduler_test.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: net/spdy/http2_write_scheduler.h
diff --git a/net/spdy/http2_write_scheduler.h b/net/spdy/http2_write_scheduler.h
index e2b0372e12bb633f3bae2fe35a902a9531c0f53b..2bf5aee0cf51124b18d3084ff3176cb00f30f1fd 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 <stdint.h>
+
+#include <algorithm>
#include <cmath>
#include <deque>
#include <map>
#include <queue>
#include <set>
+#include <unordered_map>
#include <utility>
#include <vector>
#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 <typename NodeId>
-class SpdyPriorityTreePeer;
+template <typename StreamIdType>
+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 <typename NodeId>
-class SpdyPriorityTree {
+template <typename StreamIdType>
+class Http2PriorityWriteScheduler {
public:
- typedef std::pair<NodeId, float> PriorityNode;
- typedef std::vector<PriorityNode> PriorityList;
-
- SpdyPriorityTree();
-
- // Orders in descending order of priority.
- struct NodePriorityComparator {
- bool operator ()(const std::pair<NodeId, float>& lhs,
- const std::pair<NodeId, float>& rhs);
- };
-
- friend class test::SpdyPriorityTreePeer<NodeId>;
-
- // 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<NodeId> 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<StreamIdType>;
+
+ // 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<StreamIdType> 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<Node*> NodeVector;
- typedef std::map<NodeId, Node*> NodeMap;
-
- struct Node {
- // ID for this node.
- NodeId id;
- // ID of parent node.
- Node* parent = nullptr;
+ struct StreamInfo;
+ using StreamInfoVector = std::vector<StreamInfo*>;
+ using StreamInfoMap = std::unordered_map<StreamIdType, StreamInfo*>;
+
+ struct StreamInfo : public base::LinkNode<StreamInfo> {
+ // 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<NodeMap> 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<StreamInfoMap> 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<StreamInfo> 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 <typename NodeId>
-SpdyPriorityTree<NodeId>::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 <typename NodeId>
-bool SpdyPriorityTree<NodeId>::NodePriorityComparator::operator()(
- const std::pair<NodeId, float>& lhs,
- const std::pair<NodeId, float>& rhs) {
- return lhs.second > rhs.second;
+template <typename StreamIdType>
+Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-int SpdyPriorityTree<NodeId>::num_nodes() const {
- return all_nodes_.size();
+template <typename StreamIdType>
+int Http2PriorityWriteScheduler<StreamIdType>::num_streams() const {
+ return all_stream_infos_.size();
}
-template <typename NodeId>
-bool SpdyPriorityTree<NodeId>::NodeExists(NodeId node_id) const {
- return ContainsKey(all_nodes_, node_id);
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::StreamRegistered(
+ StreamIdType stream_id) const {
+ return ContainsKey(all_stream_infos_, stream_id);
}
-template <typename NodeId>
-bool SpdyPriorityTree<NodeId>::AddNode(NodeId node_id,
- NodeId parent_id,
- int weight,
- bool exclusive) {
- if (NodeExists(node_id) || weight < kMinWeight || weight > kMaxWeight) {
- return false;
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-bool SpdyPriorityTree<NodeId>::RemoveNode(NodeId node_id) {
- if (node_id == kRootNodeId) {
- return false;
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::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<StreamInfo> 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> 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<float>(child->weight) /
- static_cast<float>(node->total_child_weights);
+ 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;
@@ -266,81 +345,111 @@ bool SpdyPriorityTree<NodeId>::RemoveNode(NodeId node_id) {
child->weight = new_weight;
parent->total_child_weights += child->weight;
}
-
- return true;
+ UpdatePrioritiesUnder(parent);
}
-template <typename NodeId>
-int SpdyPriorityTree<NodeId>::GetWeight(NodeId node_id) const {
- const Node* node = FindNode(node_id);
- return (node == nullptr) ? 0 : node->weight;
+template <typename StreamIdType>
+int Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-NodeId SpdyPriorityTree<NodeId>::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 <typename StreamIdType>
+StreamIdType Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-std::vector<NodeId> SpdyPriorityTree<NodeId>::GetChildren(
- NodeId node_id) const {
- std::vector<NodeId> child_vec;
- const Node* node = FindNode(node_id);
- if (node != nullptr) {
- child_vec.reserve(node->children.size());
- for (Node* child : node->children) {
+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) {
+ 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 <typename NodeId>
-bool SpdyPriorityTree<NodeId>::SetWeight(
- NodeId node_id, int weight) {
- if (!NodeExists(node_id)) {
- return false;
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-bool SpdyPriorityTree<NodeId>::SetParent(
- NodeId node_id, NodeId parent_id, bool exclusive) {
- if (node_id == kRootNodeId || node_id == parent_id) {
- return false;
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::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<NodeId>::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 <typename NodeId>
-bool SpdyPriorityTree<NodeId>::SetBlocked(NodeId node_id, bool blocked) {
- if (!NodeExists(node_id)) {
- return false;
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::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 <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-bool SpdyPriorityTree<NodeId>::SetReady(NodeId node_id, bool ready) {
- if (!NodeExists(node_id)) {
- return false;
+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;
+ }
}
- Node* node = all_nodes_[node_id];
- node->ready = ready;
- return true;
+ return false;
}
-template <typename NodeId>
-bool SpdyPriorityTree<NodeId>::Remove(NodeVector* nodes, const Node* node) {
- for (typename NodeVector::iterator it = nodes->begin(); it != nodes->end();
- ++it) {
- if (*it == node) {
- nodes->erase(it);
+template <typename StreamIdType>
+int Http2PriorityWriteScheduler<StreamIdType>::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 <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-void SpdyPriorityTree<NodeId>::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 <typename StreamIdType>
+const typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
+Http2PriorityWriteScheduler<StreamIdType>::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 StreamIdType>
+typename Http2PriorityWriteScheduler<StreamIdType>::StreamInfo*
+Http2PriorityWriteScheduler<StreamIdType>::FindStream(StreamIdType stream_id) {
+ typename StreamInfoMap::iterator it = all_stream_infos_.find(stream_id);
+ return it == all_stream_infos_.end() ? nullptr : it->second;
+}
+
+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->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 <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-const typename SpdyPriorityTree<NodeId>::Node*
-SpdyPriorityTree<NodeId>::FindNode(NodeId node_id) const {
- typename NodeMap::const_iterator it = all_nodes_.find(node_id);
- return (it == all_nodes_.end() ? nullptr : it->second);
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::Schedule(
+ StreamInfo* stream_info) {
+ DCHECK(!stream_info->scheduled);
+ for (base::LinkNode<StreamInfo>* 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 NodeId>
-typename SpdyPriorityTree<NodeId>::Node* SpdyPriorityTree<NodeId>::FindNode(
- NodeId node_id) {
- typename NodeMap::const_iterator it = all_nodes_.find(node_id);
- return (it == all_nodes_.end() ? nullptr : it->second);
+template <typename StreamIdType>
+void Http2PriorityWriteScheduler<StreamIdType>::Unschedule(
+ StreamInfo* stream_info) {
+ DCHECK(stream_info->scheduled);
+ stream_info->RemoveFromList();
+ stream_info->scheduled = false;
}
-template <typename NodeId>
-bool SpdyPriorityTree<NodeId>::HasChild(NodeId parent_id,
- NodeId child_id) const {
- const Node* parent = FindNode(parent_id);
+template <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::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 <typename NodeId>
-std::vector<std::pair<NodeId, float> >
-SpdyPriorityTree<NodeId>::GetPriorityList() {
- PriorityList priority_list;
-
- // Update total_writeable_child_weights to reflect the current
- // state of the tree.
- PropagateNodeState(root_node_);
-
- std::deque<Node*> 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<float>(current_node->weight) /
- static_cast<float>(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<float>(current_node->weight) /
- static_cast<float>(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 <typename StreamIdType>
+bool Http2PriorityWriteScheduler<StreamIdType>::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 <typename StreamIdType>
+StreamIdType Http2PriorityWriteScheduler<StreamIdType>::PopNextUsableStream() {
+ for (base::LinkNode<StreamInfo>* 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 <typename NodeId>
-bool SpdyPriorityTree<NodeId>::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 <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;
+ 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;
}
« no previous file with comments | « no previous file | net/spdy/http2_write_scheduler_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698