| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #ifndef NET_SPDY_SPDY_PRIORITY_TREE_H_ | |
| 6 #define NET_SPDY_SPDY_PRIORITY_TREE_H_ | |
| 7 | |
| 8 #include <cmath> | |
| 9 #include <deque> | |
| 10 #include <map> | |
| 11 #include <queue> | |
| 12 #include <set> | |
| 13 #include <utility> | |
| 14 #include <vector> | |
| 15 | |
| 16 #include "base/containers/hash_tables.h" | |
| 17 #include "base/logging.h" | |
| 18 #include "base/macros.h" | |
| 19 #include "base/memory/scoped_ptr.h" | |
| 20 #include "base/stl_util.h" | |
| 21 | |
| 22 namespace net { | |
| 23 | |
| 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 | |
| 27 // | |
| 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. | |
| 33 // | |
| 34 // The NodeId type must be a POD that supports comparison (most | |
| 35 // likely, it will be a number). | |
| 36 | |
| 37 namespace test { | |
| 38 template <typename NodeId> | |
| 39 class SpdyPriorityTreePeer; | |
| 40 } | |
| 41 | |
| 42 const int kRootNodeId = 0; | |
| 43 const int kDefaultWeight = 16; | |
| 44 const int kMinWeight = 1; | |
| 45 const int kMaxWeight = 256; | |
| 46 | |
| 47 template <typename NodeId> | |
| 48 class SpdyPriorityTree { | |
| 49 public: | |
| 50 typedef std::pair<NodeId, float> PriorityNode; | |
| 51 typedef std::vector<PriorityNode> PriorityList; | |
| 52 | |
| 53 SpdyPriorityTree(); | |
| 54 | |
| 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); | |
| 59 }; | |
| 60 | |
| 61 friend class test::SpdyPriorityTreePeer<NodeId>; | |
| 62 | |
| 63 // Return the number of nodes currently in the tree. | |
| 64 int num_nodes() const; | |
| 65 | |
| 66 // Return true if the tree contains a node with the given ID. | |
| 67 bool NodeExists(NodeId node_id) const; | |
| 68 | |
| 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); | |
| 76 | |
| 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); | |
| 80 | |
| 81 // Get the weight of the given node. | |
| 82 int GetWeight(NodeId node_id) const; | |
| 83 | |
| 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; | |
| 87 | |
| 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; | |
| 91 | |
| 92 // Set the priority of the given node. | |
| 93 bool SetWeight(NodeId node_id, int weight); | |
| 94 | |
| 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); | |
| 102 | |
| 103 // Returns true if the node parent_id has child_id in its children. | |
| 104 bool HasChild(NodeId parent_id, NodeId child_id) const; | |
| 105 | |
| 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); | |
| 109 | |
| 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); | |
| 114 | |
| 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(); | |
| 119 | |
| 120 private: | |
| 121 struct Node; | |
| 122 typedef std::vector<Node*> NodeVector; | |
| 123 typedef std::map<NodeId, Node*> NodeMap; | |
| 124 | |
| 125 struct Node { | |
| 126 // ID for this node. | |
| 127 NodeId id; | |
| 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. | |
| 140 NodeVector children; | |
| 141 // Is the associated stream write-blocked? | |
| 142 bool blocked = false; | |
| 143 // Does the stream have data ready for writing? | |
| 144 bool ready = false; | |
| 145 // The fraction of resources to dedicate to this node. | |
| 146 float priority = 0; | |
| 147 }; | |
| 148 | |
| 149 static bool Remove(NodeVector* nodes, const Node* node); | |
| 150 | |
| 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); | |
| 154 | |
| 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); | |
| 158 | |
| 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; | |
| 162 | |
| 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_; | |
| 166 | |
| 167 DISALLOW_COPY_AND_ASSIGN(SpdyPriorityTree); | |
| 168 }; | |
| 169 | |
| 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_; | |
| 180 } | |
| 181 | |
| 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; | |
| 187 } | |
| 188 | |
| 189 template <typename NodeId> | |
| 190 int SpdyPriorityTree<NodeId>::num_nodes() const { | |
| 191 return all_nodes_.size(); | |
| 192 } | |
| 193 | |
| 194 template <typename NodeId> | |
| 195 bool SpdyPriorityTree<NodeId>::NodeExists(NodeId node_id) const { | |
| 196 return ContainsKey(all_nodes_, node_id); | |
| 197 } | |
| 198 | |
| 199 template <typename NodeId> | |
| 200 bool SpdyPriorityTree<NodeId>::AddNode(NodeId node_id, | |
| 201 NodeId parent_id, | |
| 202 int weight, | |
| 203 bool exclusive) { | |
| 204 if (NodeExists(node_id) || weight < kMinWeight || weight > kMaxWeight) { | |
| 205 return false; | |
| 206 } | |
| 207 Node* parent = FindNode(parent_id); | |
| 208 if (parent == nullptr) { | |
| 209 return false; | |
| 210 } | |
| 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; | |
| 216 if (exclusive) { | |
| 217 // Move the parent's current children below the new node. | |
| 218 using std::swap; | |
| 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; | |
| 224 } | |
| 225 // Clear parent's old child data. | |
| 226 DCHECK(parent->children.empty()); | |
| 227 parent->total_child_weights = 0; | |
| 228 } | |
| 229 // Add new node to parent. | |
| 230 parent->children.push_back(new_node); | |
| 231 parent->total_child_weights += weight; | |
| 232 return true; | |
| 233 } | |
| 234 | |
| 235 template <typename NodeId> | |
| 236 bool SpdyPriorityTree<NodeId>::RemoveNode(NodeId node_id) { | |
| 237 if (node_id == kRootNodeId) { | |
| 238 return false; | |
| 239 } | |
| 240 // Remove the node from table. | |
| 241 typename NodeMap::iterator it = all_nodes_.find(node_id); | |
| 242 if (it == all_nodes_.end()) { | |
| 243 return false; | |
| 244 } | |
| 245 scoped_ptr<Node> node(it->second); | |
| 246 all_nodes_.erase(it); | |
| 247 | |
| 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; | |
| 252 | |
| 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) { | |
| 264 new_weight = 1; | |
| 265 } | |
| 266 child->weight = new_weight; | |
| 267 parent->total_child_weights += child->weight; | |
| 268 } | |
| 269 | |
| 270 return true; | |
| 271 } | |
| 272 | |
| 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; | |
| 277 } | |
| 278 | |
| 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 | |
| 284 : node->parent->id; | |
| 285 } | |
| 286 | |
| 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); | |
| 296 } | |
| 297 } | |
| 298 return child_vec; | |
| 299 } | |
| 300 | |
| 301 template <typename NodeId> | |
| 302 bool SpdyPriorityTree<NodeId>::SetWeight( | |
| 303 NodeId node_id, int weight) { | |
| 304 if (!NodeExists(node_id)) { | |
| 305 return false; | |
| 306 } | |
| 307 if (weight < kMinWeight || weight > kMaxWeight) { | |
| 308 return false; | |
| 309 } | |
| 310 | |
| 311 Node* node = all_nodes_[node_id]; | |
| 312 if (node->parent != nullptr) { | |
| 313 node->parent->total_child_weights += (weight - node->weight); | |
| 314 } | |
| 315 node->weight = weight; | |
| 316 | |
| 317 return true; | |
| 318 } | |
| 319 | |
| 320 | |
| 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) { | |
| 325 return false; | |
| 326 } | |
| 327 Node* node = FindNode(node_id); | |
| 328 Node* new_parent = FindNode(parent_id); | |
| 329 if (node == nullptr || new_parent == nullptr) { | |
| 330 return false; | |
| 331 } | |
| 332 | |
| 333 // If the new parent is already the node's parent, we're done. | |
| 334 if (node->parent == new_parent) { | |
| 335 return true; | |
| 336 } | |
| 337 | |
| 338 // Next, check to see if the new parent is currently a descendant | |
| 339 // of the node. | |
| 340 Node* last = new_parent->parent; | |
| 341 bool cycle_exists = false; | |
| 342 while (last != nullptr) { | |
| 343 if (last == node) { | |
| 344 cycle_exists = true; | |
| 345 break; | |
| 346 } | |
| 347 last = last->parent; | |
| 348 } | |
| 349 | |
| 350 if (cycle_exists) { | |
| 351 // The new parent moves to the level of the current node. | |
| 352 SetParent(parent_id, node->parent->id, false); | |
| 353 } | |
| 354 | |
| 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; | |
| 359 | |
| 360 if (exclusive) { | |
| 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); | |
| 365 } | |
| 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; | |
| 370 } | |
| 371 | |
| 372 // Make the change. | |
| 373 node->parent = new_parent; | |
| 374 new_parent->children.push_back(node); | |
| 375 new_parent->total_child_weights += node->weight; | |
| 376 return true; | |
| 377 } | |
| 378 | |
| 379 template <typename NodeId> | |
| 380 bool SpdyPriorityTree<NodeId>::SetBlocked(NodeId node_id, bool blocked) { | |
| 381 if (!NodeExists(node_id)) { | |
| 382 return false; | |
| 383 } | |
| 384 | |
| 385 Node* node = all_nodes_[node_id]; | |
| 386 node->blocked = blocked; | |
| 387 return true; | |
| 388 } | |
| 389 | |
| 390 template <typename NodeId> | |
| 391 bool SpdyPriorityTree<NodeId>::SetReady(NodeId node_id, bool ready) { | |
| 392 if (!NodeExists(node_id)) { | |
| 393 return false; | |
| 394 } | |
| 395 Node* node = all_nodes_[node_id]; | |
| 396 node->ready = ready; | |
| 397 return true; | |
| 398 } | |
| 399 | |
| 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(); | |
| 403 ++it) { | |
| 404 if (*it == node) { | |
| 405 nodes->erase(it); | |
| 406 return true; | |
| 407 } | |
| 408 } | |
| 409 return false; | |
| 410 } | |
| 411 | |
| 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); | |
| 418 } | |
| 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; | |
| 423 } | |
| 424 } | |
| 425 | |
| 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); | |
| 431 } | |
| 432 | |
| 433 template <typename NodeId> | |
| 434 typename SpdyPriorityTree<NodeId>::Node* SpdyPriorityTree<NodeId>::FindNode( | |
| 435 NodeId node_id) { | |
| 436 typename NodeMap::const_iterator it = all_nodes_.find(node_id); | |
| 437 return (it == all_nodes_.end() ? nullptr : it->second); | |
| 438 } | |
| 439 | |
| 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) { | |
| 445 return false; | |
| 446 } | |
| 447 auto found = | |
| 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(); | |
| 451 } | |
| 452 | |
| 453 template <typename NodeId> | |
| 454 std::vector<std::pair<NodeId, float> > | |
| 455 SpdyPriorityTree<NodeId>::GetPriorityList() { | |
| 456 PriorityList priority_list; | |
| 457 | |
| 458 // Update total_writeable_child_weights to reflect the current | |
| 459 // state of the tree. | |
| 460 PropagateNodeState(root_node_); | |
| 461 | |
| 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); | |
| 467 } | |
| 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 | |
| 475 // to this subtree. | |
| 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); | |
| 482 } | |
| 483 } else { | |
| 484 // There's nothing to see in this subtree. | |
| 485 current_node->priority = 0; | |
| 486 } | |
| 487 } else { | |
| 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)); | |
| 495 } | |
| 496 // Remove this node from the queue. | |
| 497 queue.pop_front(); | |
| 498 } | |
| 499 | |
| 500 // Sort the nodes in descending order of priority. | |
| 501 std::sort(priority_list.begin(), priority_list.end(), | |
| 502 NodePriorityComparator()); | |
| 503 | |
| 504 return priority_list; | |
| 505 } | |
| 506 | |
| 507 template <typename NodeId> | |
| 508 bool SpdyPriorityTree<NodeId>::ValidateInvariantsForTests() const { | |
| 509 int total_nodes = 0; | |
| 510 int nodes_visited = 0; | |
| 511 // Iterate through all nodes in the map. | |
| 512 for (const auto& kv : all_nodes_) { | |
| 513 ++total_nodes; | |
| 514 ++nodes_visited; | |
| 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 | |
| 521 << " as its child."; | |
| 522 return false; | |
| 523 } | |
| 524 | |
| 525 if (!node.children.empty()) { | |
| 526 int total_child_weights = 0; | |
| 527 // Iterate through the node's children. | |
| 528 for (Node* child : node.children) { | |
| 529 ++nodes_visited; | |
| 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."; | |
| 535 return false; | |
| 536 } | |
| 537 total_child_weights += child->weight; | |
| 538 } | |
| 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; | |
| 545 return false; | |
| 546 } | |
| 547 } | |
| 548 } | |
| 549 | |
| 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."; | |
| 553 return false; | |
| 554 } | |
| 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); | |
| 558 return true; | |
| 559 } | |
| 560 | |
| 561 } // namespace net | |
| 562 | |
| 563 #endif // NET_SPDY_SPDY_PRIORITY_TREE_H_ | |
| OLD | NEW |