1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #ifndef NET_SPDY_SPDY_PRIORITY_TREE_H_
6 #define NET_SPDY_SPDY_PRIORITY_TREE_H_
16 #include "base/basictypes.h"
17 #include "base/containers/hash_tables.h"
18 #include "base/logging.h"
19 #include "base/memory/scoped_ptr.h"
20 #include "base/stl_util.h"
24 // This data structure implements the HTTP/2 stream priority tree defined in
25 // section 5.3 of RFC 7540:
26 // http://tools.ietf.org/html/rfc7540#section-5.3
28 // Nodes can be added and removed, and dependencies between them defined.
29 // Nodes constitute a tree rooted at node ID 0: each node has a single parent
30 // node, and 0 or more child nodes. Individual nodes can be marked as ready to
31 // read/write, and then the whole structure can be queried to pick the next
32 // node to read/write out of those that are ready.
34 // The NodeId type must be a POD that supports comparison (most
35 // likely, it will be a number).
38 template <typename NodeId
>
39 class SpdyPriorityTreePeer
;
42 const int kRootNodeId
= 0;
43 const int kDefaultWeight
= 16;
44 const int kMinWeight
= 1;
45 const int kMaxWeight
= 256;
47 template <typename NodeId
>
48 class SpdyPriorityTree
{
50 typedef std::pair
<NodeId
, float> PriorityNode
;
51 typedef std::vector
<PriorityNode
> PriorityList
;
55 // Orders in descending order of priority.
56 struct NodePriorityComparator
{
57 bool operator ()(const std::pair
<NodeId
, float>& lhs
,
58 const std::pair
<NodeId
, float>& rhs
);
61 friend class test::SpdyPriorityTreePeer
<NodeId
>;
63 // Return the number of nodes currently in the tree.
64 int num_nodes() const;
66 // Return true if the tree contains a node with the given ID.
67 bool NodeExists(NodeId node_id
) const;
69 // Add a new node with the given weight and parent. Non-exclusive nodes
70 // simply get added below the parent node. If exclusive = true, the node
71 // becomes the parent's sole child and the parent's previous children
72 // become the children of the new node.
73 // Returns true on success. Returns false if the node already exists
74 // in the tree, or if the parent node does not exist.
75 bool AddNode(NodeId node_id
, NodeId parent_id
, int weight
, bool exclusive
);
77 // Remove an existing node from the tree. Returns true on success, or
78 // false if the node doesn't exist.
79 bool RemoveNode(NodeId node_id
);
81 // Get the weight of the given node.
82 int GetWeight(NodeId node_id
) const;
84 // Get the parent of the given node. If the node doesn't exist, or is a root
85 // node (and thus has no parent), returns NodeId().
86 NodeId
GetParent(NodeId node_id
) const;
88 // Get the children of the given node. If the node doesn't exist, or has no
89 // child, returns empty vector.
90 std::vector
<NodeId
> GetChildren(NodeId node_id
) const;
92 // Set the priority of the given node.
93 bool SetWeight(NodeId node_id
, int weight
);
95 // Set the parent of the given node. Returns true on success.
96 // Returns false and has no effect if the node and/or the parent doesn't
97 // exist. If the new parent is a descendant of the node (i.e. this would have
98 // created a cycle) then we rearrange the topology of the tree as described
99 // in section 5.3.3 of RFC 7540:
100 // https://tools.ietf.org/html/rfc7540#section-5.3.3
101 bool SetParent(NodeId node_id
, NodeId parent_id
, bool exclusive
);
103 // Returns true if the node parent_id has child_id in its children.
104 bool HasChild(NodeId parent_id
, NodeId child_id
) const;
106 // Mark a node as blocked or unblocked. Return true on success, or false
107 // if unable to mark the specified node.
108 bool SetBlocked(NodeId node_id
, bool blocked
);
110 // Mark whether or not a node is ready to write; i.e. whether there is
111 // buffered data for the associated stream. Return true on success, or false
112 // if unable to mark the specified node.
113 bool SetReady(NodeId node_id
, bool ready
);
115 // Returns an ordered list of writeable nodes and their priorities.
116 // Priority is calculated as:
117 // parent's priority * (node's weight / sum of sibling weights)
118 PriorityList
GetPriorityList();
122 typedef std::vector
<Node
*> NodeVector
;
123 typedef std::map
<NodeId
, Node
*> NodeMap
;
128 // ID of parent node.
129 Node
* parent
= nullptr;
130 // Weights can range between 1 and 256 (inclusive).
131 int weight
= kDefaultWeight
;
132 // The total weight of this node's direct descendants.
133 int total_child_weights
= 0;
134 // The total weight of direct descendants that are writeable
135 // (ready to write and not blocked). This value does not necessarily
136 // reflect the current state of the tree; instead, we lazily update it
137 // on calls to PropagateNodeState().
138 int total_writeable_child_weights
= 0;
139 // Pointers to nodes for children, if any.
141 // Is the associated stream write-blocked?
142 bool blocked
= false;
143 // Does the stream have data ready for writing?
145 // The fraction of resources to dedicate to this node.
149 static bool Remove(NodeVector
* nodes
, const Node
* node
);
151 // Update the value of total_writeable_child_weights for the given node
152 // to reflect the current state of the tree.
153 void PropagateNodeState(Node
* node
);
155 // Get the given node, or return nullptr if it doesn't exist.
156 const Node
* FindNode(NodeId node_id
) const;
157 Node
* FindNode(NodeId node_id
);
159 // Return true if all internal invariants hold (useful for unit tests).
160 // Unless there are bugs, this should always return true.
161 bool ValidateInvariantsForTests() const;
163 Node
* root_node_
; // pointee owned by all_nodes_
164 NodeMap all_nodes_
; // maps from node IDs to Node objects
165 STLValueDeleter
<NodeMap
> all_nodes_deleter_
;
167 DISALLOW_COPY_AND_ASSIGN(SpdyPriorityTree
);
170 template <typename NodeId
>
171 SpdyPriorityTree
<NodeId
>::SpdyPriorityTree()
172 : all_nodes_deleter_(&all_nodes_
) {
173 root_node_
= new Node();
174 root_node_
->id
= kRootNodeId
;
175 root_node_
->weight
= kDefaultWeight
;
176 root_node_
->parent
= nullptr;
177 root_node_
->priority
= 1.0;
178 root_node_
->ready
= true;
179 all_nodes_
[kRootNodeId
] = root_node_
;
182 template <typename NodeId
>
183 bool SpdyPriorityTree
<NodeId
>::NodePriorityComparator::operator()(
184 const std::pair
<NodeId
, float>& lhs
,
185 const std::pair
<NodeId
, float>& rhs
) {
186 return lhs
.second
> rhs
.second
;
189 template <typename NodeId
>
190 int SpdyPriorityTree
<NodeId
>::num_nodes() const {
191 return all_nodes_
.size();
194 template <typename NodeId
>
195 bool SpdyPriorityTree
<NodeId
>::NodeExists(NodeId node_id
) const {
196 return ContainsKey(all_nodes_
, node_id
);
199 template <typename NodeId
>
200 bool SpdyPriorityTree
<NodeId
>::AddNode(NodeId node_id
,
204 if (NodeExists(node_id
) || weight
< kMinWeight
|| weight
> kMaxWeight
) {
207 Node
* parent
= FindNode(parent_id
);
208 if (parent
== nullptr) {
211 Node
* new_node
= new Node
;
212 new_node
->id
= node_id
;
213 new_node
->weight
= weight
;
214 new_node
->parent
= parent
;
215 all_nodes_
[node_id
] = new_node
;
217 // Move the parent's current children below the new node.
219 swap(new_node
->children
, parent
->children
);
220 new_node
->total_child_weights
= parent
->total_child_weights
;
221 // Update each child's parent.
222 for (Node
* child
: new_node
->children
) {
223 child
->parent
= new_node
;
225 // Clear parent's old child data.
226 DCHECK(parent
->children
.empty());
227 parent
->total_child_weights
= 0;
229 // Add new node to parent.
230 parent
->children
.push_back(new_node
);
231 parent
->total_child_weights
+= weight
;
235 template <typename NodeId
>
236 bool SpdyPriorityTree
<NodeId
>::RemoveNode(NodeId node_id
) {
237 if (node_id
== kRootNodeId
) {
240 // Remove the node from table.
241 typename
NodeMap::iterator it
= all_nodes_
.find(node_id
);
242 if (it
== all_nodes_
.end()) {
245 scoped_ptr
<Node
> node(it
->second
);
246 all_nodes_
.erase(it
);
248 Node
* parent
= node
->parent
;
249 // Remove the node from parent's child list.
250 Remove(&parent
->children
, node
.get());
251 parent
->total_child_weights
-= node
->weight
;
253 // Move the node's children to the parent's child list.
254 // Update each child's parent and weight.
255 for (Node
* child
: node
->children
) {
256 child
->parent
= parent
;
257 parent
->children
.push_back(child
);
258 // Divide the removed node's weight among its children, rounding to the
259 // nearest valid weight.
260 float float_weight
= node
->weight
* static_cast<float>(child
->weight
) /
261 static_cast<float>(node
->total_child_weights
);
262 int new_weight
= floor(float_weight
+ 0.5);
263 if (new_weight
== 0) {
266 child
->weight
= new_weight
;
267 parent
->total_child_weights
+= child
->weight
;
273 template <typename NodeId
>
274 int SpdyPriorityTree
<NodeId
>::GetWeight(NodeId node_id
) const {
275 const Node
* node
= FindNode(node_id
);
276 return (node
== nullptr) ? 0 : node
->weight
;
279 template <typename NodeId
>
280 NodeId SpdyPriorityTree
<NodeId
>::GetParent(NodeId node_id
) const {
281 const Node
* node
= FindNode(node_id
);
282 // Root node has null parent.
283 return (node
== nullptr || node
->parent
== nullptr) ? kRootNodeId
287 template <typename NodeId
>
288 std::vector
<NodeId
> SpdyPriorityTree
<NodeId
>::GetChildren(
289 NodeId node_id
) const {
290 std::vector
<NodeId
> child_vec
;
291 const Node
* node
= FindNode(node_id
);
292 if (node
!= nullptr) {
293 child_vec
.reserve(node
->children
.size());
294 for (Node
* child
: node
->children
) {
295 child_vec
.push_back(child
->id
);
301 template <typename NodeId
>
302 bool SpdyPriorityTree
<NodeId
>::SetWeight(
303 NodeId node_id
, int weight
) {
304 if (!NodeExists(node_id
)) {
307 if (weight
< kMinWeight
|| weight
> kMaxWeight
) {
311 Node
* node
= all_nodes_
[node_id
];
312 if (node
->parent
!= nullptr) {
313 node
->parent
->total_child_weights
+= (weight
- node
->weight
);
315 node
->weight
= weight
;
321 template <typename NodeId
>
322 bool SpdyPriorityTree
<NodeId
>::SetParent(
323 NodeId node_id
, NodeId parent_id
, bool exclusive
) {
324 if (node_id
== kRootNodeId
|| node_id
== parent_id
) {
327 Node
* node
= FindNode(node_id
);
328 Node
* new_parent
= FindNode(parent_id
);
329 if (node
== nullptr || new_parent
== nullptr) {
333 // If the new parent is already the node's parent, we're done.
334 if (node
->parent
== new_parent
) {
338 // Next, check to see if the new parent is currently a descendant
340 Node
* last
= new_parent
->parent
;
341 bool cycle_exists
= false;
342 while (last
!= nullptr) {
351 // The new parent moves to the level of the current node.
352 SetParent(parent_id
, node
->parent
->id
, false);
355 // Remove node from old parent's child list.
356 Node
* old_parent
= node
->parent
;
357 Remove(&old_parent
->children
, node
);
358 old_parent
->total_child_weights
-= node
->weight
;
361 // Move the new parent's current children below the current node.
362 for (Node
* child
: new_parent
->children
) {
363 child
->parent
= node
;
364 node
->children
.push_back(child
);
366 node
->total_child_weights
+= new_parent
->total_child_weights
;
367 // Clear new parent's old child data.
368 new_parent
->children
.clear();
369 new_parent
->total_child_weights
= 0;
373 node
->parent
= new_parent
;
374 new_parent
->children
.push_back(node
);
375 new_parent
->total_child_weights
+= node
->weight
;
379 template <typename NodeId
>
380 bool SpdyPriorityTree
<NodeId
>::SetBlocked(NodeId node_id
, bool blocked
) {
381 if (!NodeExists(node_id
)) {
385 Node
* node
= all_nodes_
[node_id
];
386 node
->blocked
= blocked
;
390 template <typename NodeId
>
391 bool SpdyPriorityTree
<NodeId
>::SetReady(NodeId node_id
, bool ready
) {
392 if (!NodeExists(node_id
)) {
395 Node
* node
= all_nodes_
[node_id
];
400 template <typename NodeId
>
401 bool SpdyPriorityTree
<NodeId
>::Remove(NodeVector
* nodes
, const Node
* node
) {
402 for (typename
NodeVector::iterator it
= nodes
->begin(); it
!= nodes
->end();
412 template <typename NodeId
>
413 void SpdyPriorityTree
<NodeId
>::PropagateNodeState(Node
* node
) {
414 // Reset total_writeable_child_weights to its maximum value.
415 node
->total_writeable_child_weights
= node
->total_child_weights
;
416 for (Node
* child
: node
->children
) {
417 PropagateNodeState(child
);
419 if (node
->total_writeable_child_weights
== 0 &&
420 (node
->blocked
|| !node
->ready
)) {
421 // Tell the parent that this entire subtree is unwriteable.
422 node
->parent
->total_writeable_child_weights
-= node
->weight
;
426 template <typename NodeId
>
427 const typename SpdyPriorityTree
<NodeId
>::Node
*
428 SpdyPriorityTree
<NodeId
>::FindNode(NodeId node_id
) const {
429 typename
NodeMap::const_iterator it
= all_nodes_
.find(node_id
);
430 return (it
== all_nodes_
.end() ? nullptr : it
->second
);
433 template <typename NodeId
>
434 typename SpdyPriorityTree
<NodeId
>::Node
* SpdyPriorityTree
<NodeId
>::FindNode(
436 typename
NodeMap::const_iterator it
= all_nodes_
.find(node_id
);
437 return (it
== all_nodes_
.end() ? nullptr : it
->second
);
440 template <typename NodeId
>
441 bool SpdyPriorityTree
<NodeId
>::HasChild(NodeId parent_id
,
442 NodeId child_id
) const {
443 const Node
* parent
= FindNode(parent_id
);
444 if (parent
== nullptr) {
448 std::find_if(parent
->children
.begin(), parent
->children
.end(),
449 [child_id
](Node
* node
) { return node
->id
== child_id
; });
450 return found
!= parent
->children
.end();
453 template <typename NodeId
>
454 std::vector
<std::pair
<NodeId
, float> >
455 SpdyPriorityTree
<NodeId
>::GetPriorityList() {
456 PriorityList priority_list
;
458 // Update total_writeable_child_weights to reflect the current
459 // state of the tree.
460 PropagateNodeState(root_node_
);
462 std::deque
<Node
*> queue
;
463 DCHECK(root_node_
->priority
== 1.0);
464 // Start by examining our top-level nodes.
465 for (Node
* child
: root_node_
->children
) {
466 queue
.push_back(child
);
468 while (!queue
.empty()) {
469 Node
* current_node
= queue
.front();
470 const Node
* parent_node
= current_node
->parent
;
471 if (current_node
->blocked
|| !current_node
->ready
) {
472 if (current_node
->total_writeable_child_weights
> 0) {
473 // This node isn't writeable, but it has writeable children.
474 // Calculate the total fraction of resources we can allot
476 current_node
->priority
= parent_node
->priority
*
477 (static_cast<float>(current_node
->weight
) /
478 static_cast<float>(parent_node
->total_writeable_child_weights
));
479 // Examine the children.
480 for (Node
* child
: current_node
->children
) {
481 queue
.push_back(child
);
484 // There's nothing to see in this subtree.
485 current_node
->priority
= 0;
488 // This node is writeable; calculate its priority.
489 current_node
->priority
= parent_node
->priority
*
490 (static_cast<float>(current_node
->weight
) /
491 static_cast<float>(parent_node
->total_writeable_child_weights
));
492 // Add this node to the priority list.
493 priority_list
.push_back(
494 PriorityNode(current_node
->id
, current_node
->priority
));
496 // Remove this node from the queue.
500 // Sort the nodes in descending order of priority.
501 std::sort(priority_list
.begin(), priority_list
.end(),
502 NodePriorityComparator());
504 return priority_list
;
507 template <typename NodeId
>
508 bool SpdyPriorityTree
<NodeId
>::ValidateInvariantsForTests() const {
510 int nodes_visited
= 0;
511 // Iterate through all nodes in the map.
512 for (const auto& kv
: all_nodes_
) {
515 const Node
& node
= *kv
.second
;
516 // All nodes except the root should have a parent, and should appear in
517 // the children of that parent.
518 if (node
.id
!= kRootNodeId
&& !HasChild(node
.parent
->id
, node
.id
)) {
519 DLOG(INFO
) << "Parent node " << node
.parent
->id
520 << " does not exist, or does not list node " << node
.id
525 if (!node
.children
.empty()) {
526 int total_child_weights
= 0;
527 // Iterate through the node's children.
528 for (Node
* child
: node
.children
) {
530 // Each node in the list should exist and should have this node
531 // set as its parent.
532 if (!NodeExists(child
->id
) || node
.id
!= GetParent(child
->id
)) {
533 DLOG(INFO
) << "Child node " << child
->id
<< " does not exist, "
534 << "or does not list " << node
.id
<< " as its parent.";
537 total_child_weights
+= child
->weight
;
539 // Verify that total_child_weights is correct.
540 if (total_child_weights
!= node
.total_child_weights
) {
541 DLOG(INFO
) << "Child weight totals do not agree. For node " << node
.id
542 << " total_child_weights has value "
543 << node
.total_child_weights
544 << ", expected " << total_child_weights
;
550 // Make sure num_nodes reflects the total number of nodes the map contains.
551 if (total_nodes
!= num_nodes()) {
552 DLOG(INFO
) << "Map contains incorrect number of nodes.";
555 // Validate the validation function; we should have visited each node twice
556 // (except for the root)
557 DCHECK(nodes_visited
== 2*num_nodes() - 1);
563 #endif // NET_SPDY_SPDY_PRIORITY_TREE_H_