| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013 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_FOREST_H_ | |
| 6 #define NET_SPDY_SPDY_PRIORITY_FOREST_H_ | |
| 7 | |
| 8 #include <map> | |
| 9 #include <set> | |
| 10 #include <vector> | |
| 11 | |
| 12 #include "base/basictypes.h" | |
| 13 #include "base/containers/hash_tables.h" | |
| 14 #include "base/logging.h" | |
| 15 #include "base/memory/scoped_ptr.h" | |
| 16 #include "base/rand_util.h" | |
| 17 | |
| 18 namespace net { | |
| 19 | |
| 20 // This data structure implements the SPDY prioriziation data structures | |
| 21 // defined in this document: http://go/spdy4-prioritization | |
| 22 // | |
| 23 // Nodes can be added and removed, and dependencies between them defined. Each | |
| 24 // node can have at most one parent and at most one child (forming a list), but | |
| 25 // there can be multiple lists, with each list root having its own priority. | |
| 26 // Individual nodes can also be marked as ready to read/write, and then the | |
| 27 // whole structure can be queried to pick the next node to read/write out of | |
| 28 // those ready. | |
| 29 // | |
| 30 // The NodeId and Priority types must be POD that support comparison (most | |
| 31 // likely, they will be numbers). | |
| 32 template <typename NodeId, typename Priority> | |
| 33 class SpdyPriorityForest { | |
| 34 public: | |
| 35 SpdyPriorityForest(); | |
| 36 ~SpdyPriorityForest(); | |
| 37 | |
| 38 // Return the number of nodes currently in the forest. | |
| 39 int num_nodes() const; | |
| 40 | |
| 41 // Return true if the forest contains a node with the given ID. | |
| 42 bool NodeExists(NodeId node_id) const; | |
| 43 | |
| 44 // Add a new root node to the forest, with the given priority. Returns true | |
| 45 // on success, or false if the node_id already exists within the forest. | |
| 46 bool AddRootNode(NodeId node_id, Priority priority); | |
| 47 | |
| 48 // Add a new node to the forest, with the given parent. Returns true on | |
| 49 // success. Returns false and has no effect if the new node already exists, | |
| 50 // or if the parent doesn't exist, or if the parent already has a child. | |
| 51 bool AddNonRootNode(NodeId node_id, NodeId parent_id, bool unordered); | |
| 52 | |
| 53 // Remove an existing node from the forest. Returns true on success, or | |
| 54 // false if the node doesn't exist. | |
| 55 bool RemoveNode(NodeId node_id); | |
| 56 | |
| 57 // Get the priority of the given node. If the node doesn't exist, or is not | |
| 58 // a root node (and thus has no priority), returns Priority(). | |
| 59 Priority GetPriority(NodeId node_id) const; | |
| 60 | |
| 61 // Get the parent of the given node. If the node doesn't exist, or is a root | |
| 62 // node (and thus has no parent), returns NodeId(). | |
| 63 NodeId GetParent(NodeId node_id) const; | |
| 64 | |
| 65 // Determine if the given node is unordered with respect to its parent. If | |
| 66 // the node doesn't exist, or is a root node (and thus has no parent), | |
| 67 // returns false. | |
| 68 bool IsNodeUnordered(NodeId node_id) const; | |
| 69 | |
| 70 // Get the child of the given node. If the node doesn't exist, or has no | |
| 71 // child, returns NodeId(). | |
| 72 NodeId GetChild(NodeId node_id) const; | |
| 73 | |
| 74 // Set the priority of the given node. If the node was not already a root | |
| 75 // node, this makes it a root node. Returns true on success, or false if the | |
| 76 // node doesn't exist. | |
| 77 bool SetPriority(NodeId node_id, Priority priority); | |
| 78 | |
| 79 // Set the parent of the given node. If the node was a root node, this makes | |
| 80 // it no longer a root. Returns true on success. Returns false and has no | |
| 81 // effect if (1) the node and/or the parent doesn't exist, (2) the new parent | |
| 82 // already has a different child than the node, or (3) if the new parent is a | |
| 83 // descendant of the node (so this would have created a cycle). | |
| 84 bool SetParent(NodeId node_id, NodeId parent_id, bool unordered); | |
| 85 | |
| 86 // Check if a node is marked as ready to read. Returns false if the node | |
| 87 // doesn't exist. | |
| 88 bool IsMarkedReadyToRead(NodeId node_id) const; | |
| 89 // Mark the node as ready or not ready to read. Returns true on success, or | |
| 90 // false if the node doesn't exist. | |
| 91 bool MarkReadyToRead(NodeId node_id); | |
| 92 bool MarkNoLongerReadyToRead(NodeId node_id); | |
| 93 // Return the ID of the next node that we should read, or return NodeId() if | |
| 94 // no node in the forest is ready to read. | |
| 95 NodeId NextNodeToRead(); | |
| 96 | |
| 97 // Check if a node is marked as ready to write. Returns false if the node | |
| 98 // doesn't exist. | |
| 99 bool IsMarkedReadyToWrite(NodeId node_id) const; | |
| 100 // Mark the node as ready or not ready to write. Returns true on success, or | |
| 101 // false if the node doesn't exist. | |
| 102 bool MarkReadyToWrite(NodeId node_id); | |
| 103 bool MarkNoLongerReadyToWrite(NodeId node_id); | |
| 104 // Return the ID of the next node that we should write, or return NodeId() if | |
| 105 // no node in the forest is ready to write. | |
| 106 NodeId NextNodeToWrite(); | |
| 107 | |
| 108 // Return true if all internal invariants hold (useful for unit tests). | |
| 109 // Unless there are bugs, this should always return true. | |
| 110 bool ValidateInvariantsForTests() const; | |
| 111 | |
| 112 private: | |
| 113 enum NodeType { ROOT_NODE, NONROOT_ORDERED, NONROOT_UNORDERED }; | |
| 114 struct Node { | |
| 115 Node() : type(ROOT_NODE), flags(0), child() { | |
| 116 depends_on.priority = Priority(); | |
| 117 } | |
| 118 NodeType type; | |
| 119 unsigned int flags; // bitfield of flags | |
| 120 union { | |
| 121 Priority priority; // used for root nodes | |
| 122 NodeId parent_id; // used for non-root nodes | |
| 123 } depends_on; | |
| 124 NodeId child; // node ID of child (or NodeId() for no child) | |
| 125 }; | |
| 126 | |
| 127 typedef base::hash_map<NodeId, Node> NodeMap; | |
| 128 | |
| 129 // Constants for the Node.flags bitset: | |
| 130 // kReadToRead: set for nodes that are ready for reading | |
| 131 static const unsigned int kReadyToRead = (1 << 0); | |
| 132 // kReadToWrite: set for nodes that are ready for writing | |
| 133 static const unsigned int kReadyToWrite = (1 << 1); | |
| 134 | |
| 135 // Common code for IsMarkedReadyToRead and IsMarkedReadyToWrite. | |
| 136 bool IsMarked(NodeId node_id, unsigned int flag) const; | |
| 137 // Common code for MarkReadyToRead and MarkReadyToWrite. | |
| 138 bool Mark(NodeId node_id, unsigned int flag); | |
| 139 // Common code for MarkNoLongerReadyToRead and MarkNoLongerReadyToWrite. | |
| 140 bool Unmark(NodeId node_id, unsigned int flag); | |
| 141 // Common code for NextNodeToRead and NextNodeToWrite; | |
| 142 NodeId FirstMarkedNode(unsigned int flag); | |
| 143 // Get the given node, or return NULL if it doesn't exist. | |
| 144 const Node* FindNode(NodeId node_id) const; | |
| 145 | |
| 146 NodeMap all_nodes_; // maps from node IDs to Node objects | |
| 147 | |
| 148 DISALLOW_COPY_AND_ASSIGN(SpdyPriorityForest); | |
| 149 }; | |
| 150 | |
| 151 template <typename NodeId, typename Priority> | |
| 152 SpdyPriorityForest<NodeId, Priority>::SpdyPriorityForest() {} | |
| 153 | |
| 154 template <typename NodeId, typename Priority> | |
| 155 SpdyPriorityForest<NodeId, Priority>::~SpdyPriorityForest() {} | |
| 156 | |
| 157 template <typename NodeId, typename Priority> | |
| 158 int SpdyPriorityForest<NodeId, Priority>::num_nodes() const { | |
| 159 return all_nodes_.size(); | |
| 160 } | |
| 161 | |
| 162 template <typename NodeId, typename Priority> | |
| 163 bool SpdyPriorityForest<NodeId, Priority>::NodeExists(NodeId node_id) const { | |
| 164 return all_nodes_.count(node_id) != 0; | |
| 165 } | |
| 166 | |
| 167 template <typename NodeId, typename Priority> | |
| 168 bool SpdyPriorityForest<NodeId, Priority>::AddRootNode( | |
| 169 NodeId node_id, Priority priority) { | |
| 170 if (NodeExists(node_id)) { | |
| 171 return false; | |
| 172 } | |
| 173 Node* new_node = &all_nodes_[node_id]; | |
| 174 new_node->type = ROOT_NODE; | |
| 175 new_node->depends_on.priority = priority; | |
| 176 return true; | |
| 177 } | |
| 178 | |
| 179 template <typename NodeId, typename Priority> | |
| 180 bool SpdyPriorityForest<NodeId, Priority>::AddNonRootNode( | |
| 181 NodeId node_id, NodeId parent_id, bool unordered) { | |
| 182 if (NodeExists(node_id) || !NodeExists(parent_id)) { | |
| 183 return false; | |
| 184 } | |
| 185 | |
| 186 Node* parent = &all_nodes_[parent_id]; | |
| 187 if (parent->child != NodeId()) { | |
| 188 return false; | |
| 189 } | |
| 190 | |
| 191 Node* new_node = &all_nodes_[node_id]; | |
| 192 new_node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED); | |
| 193 new_node->depends_on.parent_id = parent_id; | |
| 194 parent->child = node_id; | |
| 195 return true; | |
| 196 } | |
| 197 | |
| 198 template <typename NodeId, typename Priority> | |
| 199 bool SpdyPriorityForest<NodeId, Priority>::RemoveNode(NodeId node_id) { | |
| 200 if (!NodeExists(node_id)) { | |
| 201 return false; | |
| 202 } | |
| 203 const Node& node = all_nodes_[node_id]; | |
| 204 | |
| 205 // If the node to be removed is not a root node, we need to change its | |
| 206 // parent's child ID. | |
| 207 if (node.type != ROOT_NODE) { | |
| 208 DCHECK(NodeExists(node.depends_on.parent_id)); | |
| 209 Node* parent = &all_nodes_[node.depends_on.parent_id]; | |
| 210 DCHECK_EQ(node_id, parent->child); | |
| 211 parent->child = node.child; | |
| 212 } | |
| 213 | |
| 214 // If the node has a child, we need to change the child's priority or parent. | |
| 215 if (node.child != NodeId()) { | |
| 216 DCHECK(NodeExists(node.child)); | |
| 217 Node* child = &all_nodes_[node.child]; | |
| 218 DCHECK_NE(ROOT_NODE, child->type); | |
| 219 DCHECK_EQ(node_id, child->depends_on.parent_id); | |
| 220 // Make the child's new depends_on be the node's depends_on (whether that | |
| 221 // be a priority or a parent node ID). | |
| 222 child->depends_on = node.depends_on; | |
| 223 // If the removed node was a root, its child is now a root. Otherwise, the | |
| 224 // child will be be unordered if and only if it was already unordered and | |
| 225 // the removed not is also not ordered. | |
| 226 if (node.type == ROOT_NODE) { | |
| 227 child->type = ROOT_NODE; | |
| 228 } else if (node.type == NONROOT_ORDERED) { | |
| 229 child->type = NONROOT_ORDERED; | |
| 230 } | |
| 231 } | |
| 232 | |
| 233 // Delete the node. | |
| 234 all_nodes_.erase(node_id); | |
| 235 return true; | |
| 236 } | |
| 237 | |
| 238 template <typename NodeId, typename Priority> | |
| 239 Priority SpdyPriorityForest<NodeId, Priority>::GetPriority( | |
| 240 NodeId node_id) const { | |
| 241 const Node* node = FindNode(node_id); | |
| 242 if (node != NULL && node->type == ROOT_NODE) { | |
| 243 return node->depends_on.priority; | |
| 244 } else { | |
| 245 return Priority(); | |
| 246 } | |
| 247 } | |
| 248 | |
| 249 template <typename NodeId, typename Priority> | |
| 250 NodeId SpdyPriorityForest<NodeId, Priority>::GetParent(NodeId node_id) const { | |
| 251 const Node* node = FindNode(node_id); | |
| 252 if (node != NULL && node->type != ROOT_NODE) { | |
| 253 return node->depends_on.parent_id; | |
| 254 } else { | |
| 255 return NodeId(); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 template <typename NodeId, typename Priority> | |
| 260 bool SpdyPriorityForest<NodeId, Priority>::IsNodeUnordered( | |
| 261 NodeId node_id) const { | |
| 262 const Node* node = FindNode(node_id); | |
| 263 return node != NULL && node->type == NONROOT_UNORDERED; | |
| 264 } | |
| 265 | |
| 266 template <typename NodeId, typename Priority> | |
| 267 NodeId SpdyPriorityForest<NodeId, Priority>::GetChild(NodeId node_id) const { | |
| 268 const Node* node = FindNode(node_id); | |
| 269 if (node != NULL) { | |
| 270 return node->child; | |
| 271 } else { | |
| 272 return NodeId(); | |
| 273 } | |
| 274 } | |
| 275 | |
| 276 template <typename NodeId, typename Priority> | |
| 277 bool SpdyPriorityForest<NodeId, Priority>::SetPriority( | |
| 278 NodeId node_id, Priority priority) { | |
| 279 if (!NodeExists(node_id)) { | |
| 280 return false; | |
| 281 } | |
| 282 | |
| 283 Node* node = &all_nodes_[node_id]; | |
| 284 // If this is not already a root node, we need to make it be a root node. | |
| 285 if (node->type != ROOT_NODE) { | |
| 286 DCHECK(NodeExists(node->depends_on.parent_id)); | |
| 287 Node* parent = &all_nodes_[node->depends_on.parent_id]; | |
| 288 parent->child = NodeId(); | |
| 289 node->type = ROOT_NODE; | |
| 290 } | |
| 291 | |
| 292 node->depends_on.priority = priority; | |
| 293 return true; | |
| 294 } | |
| 295 | |
| 296 template <typename NodeId, typename Priority> | |
| 297 bool SpdyPriorityForest<NodeId, Priority>::SetParent( | |
| 298 NodeId node_id, NodeId parent_id, bool unordered) { | |
| 299 if (!NodeExists(node_id) || !NodeExists(parent_id)) { | |
| 300 return false; | |
| 301 } | |
| 302 | |
| 303 Node* node = &all_nodes_[node_id]; | |
| 304 Node* new_parent = &all_nodes_[parent_id]; | |
| 305 // If the new parent is already the node's parent, all we have to do is | |
| 306 // update the node type and we're done. | |
| 307 if (new_parent->child == node_id) { | |
| 308 node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED); | |
| 309 return true; | |
| 310 } | |
| 311 // Otherwise, if the new parent already has a child, we fail. | |
| 312 if (new_parent->child != NodeId()) { | |
| 313 return false; | |
| 314 } | |
| 315 | |
| 316 // Next, make sure we won't create a cycle. | |
| 317 if (node_id == parent_id) return false; | |
| 318 Node* last = node; | |
| 319 NodeId last_id = node_id; | |
| 320 while (last->child != NodeId()) { | |
| 321 if (last->child == parent_id) return false; | |
| 322 last_id = last->child; | |
| 323 DCHECK(NodeExists(last_id)); | |
| 324 last = &all_nodes_[last_id]; | |
| 325 } | |
| 326 | |
| 327 // If the node is not a root, we need clear its old parent's child field | |
| 328 // (unless the old parent is the same as the new parent). | |
| 329 if (node->type != ROOT_NODE) { | |
| 330 const NodeId old_parent_id = node->depends_on.parent_id; | |
| 331 DCHECK(NodeExists(old_parent_id)); | |
| 332 DCHECK(old_parent_id != parent_id); | |
| 333 Node* old_parent = &all_nodes_[old_parent_id]; | |
| 334 DCHECK_EQ(node_id, old_parent->child); | |
| 335 old_parent->child = NodeId(); | |
| 336 } | |
| 337 | |
| 338 // Make the change. | |
| 339 node->type = (unordered ? NONROOT_UNORDERED : NONROOT_ORDERED); | |
| 340 node->depends_on.parent_id = parent_id; | |
| 341 new_parent->child = node_id; | |
| 342 return true; | |
| 343 } | |
| 344 | |
| 345 template <typename NodeId, typename Priority> | |
| 346 bool SpdyPriorityForest<NodeId, Priority>::IsMarkedReadyToRead( | |
| 347 NodeId node_id) const { | |
| 348 return IsMarked(node_id, kReadyToRead); | |
| 349 } | |
| 350 | |
| 351 template <typename NodeId, typename Priority> | |
| 352 bool SpdyPriorityForest<NodeId, Priority>::MarkReadyToRead(NodeId node_id) { | |
| 353 return Mark(node_id, kReadyToRead); | |
| 354 } | |
| 355 | |
| 356 template <typename NodeId, typename Priority> | |
| 357 bool SpdyPriorityForest<NodeId, Priority>::MarkNoLongerReadyToRead( | |
| 358 NodeId node_id) { | |
| 359 return Unmark(node_id, kReadyToRead); | |
| 360 } | |
| 361 | |
| 362 template <typename NodeId, typename Priority> | |
| 363 NodeId SpdyPriorityForest<NodeId, Priority>::NextNodeToRead() { | |
| 364 return FirstMarkedNode(kReadyToRead); | |
| 365 } | |
| 366 | |
| 367 template <typename NodeId, typename Priority> | |
| 368 bool SpdyPriorityForest<NodeId, Priority>::IsMarkedReadyToWrite( | |
| 369 NodeId node_id) const { | |
| 370 return IsMarked(node_id, kReadyToWrite); | |
| 371 } | |
| 372 | |
| 373 template <typename NodeId, typename Priority> | |
| 374 bool SpdyPriorityForest<NodeId, Priority>::MarkReadyToWrite(NodeId node_id) { | |
| 375 return Mark(node_id, kReadyToWrite); | |
| 376 } | |
| 377 | |
| 378 template <typename NodeId, typename Priority> | |
| 379 bool SpdyPriorityForest<NodeId, Priority>::MarkNoLongerReadyToWrite( | |
| 380 NodeId node_id) { | |
| 381 return Unmark(node_id, kReadyToWrite); | |
| 382 } | |
| 383 | |
| 384 template <typename NodeId, typename Priority> | |
| 385 NodeId SpdyPriorityForest<NodeId, Priority>::NextNodeToWrite() { | |
| 386 return FirstMarkedNode(kReadyToWrite); | |
| 387 } | |
| 388 | |
| 389 template <typename NodeId, typename Priority> | |
| 390 bool SpdyPriorityForest<NodeId, Priority>::IsMarked( | |
| 391 NodeId node_id, unsigned int flag) const { | |
| 392 const Node* node = FindNode(node_id); | |
| 393 return node != NULL && (node->flags & flag) != 0; | |
| 394 } | |
| 395 | |
| 396 template <typename NodeId, typename Priority> | |
| 397 bool SpdyPriorityForest<NodeId, Priority>::Mark( | |
| 398 NodeId node_id, unsigned int flag) { | |
| 399 if (!NodeExists(node_id)) { | |
| 400 return false; | |
| 401 } | |
| 402 all_nodes_[node_id].flags |= flag; | |
| 403 return true; | |
| 404 } | |
| 405 | |
| 406 template <typename NodeId, typename Priority> | |
| 407 bool SpdyPriorityForest<NodeId, Priority>::Unmark( | |
| 408 NodeId node_id, unsigned int flag) { | |
| 409 if (!NodeExists(node_id)) { | |
| 410 return false; | |
| 411 } | |
| 412 all_nodes_[node_id].flags &= ~flag; | |
| 413 return true; | |
| 414 } | |
| 415 | |
| 416 template <typename NodeId, typename Priority> | |
| 417 NodeId SpdyPriorityForest<NodeId, Priority>::FirstMarkedNode( | |
| 418 unsigned int flag) { | |
| 419 // TODO(mdsteele): This is an *incredibly* stupid brute force solution. | |
| 420 | |
| 421 // Get all root nodes that have at least one marked child. | |
| 422 uint64 total_weight = 0; | |
| 423 std::map<uint64, NodeId> roots; // maps cumulative weight to root node ID | |
| 424 for (typename NodeMap::const_iterator iter = all_nodes_.begin(); | |
| 425 iter != all_nodes_.end(); ++iter) { | |
| 426 const NodeId root_id = iter->first; | |
| 427 const Node& root = iter->second; | |
| 428 if (root.type == ROOT_NODE) { | |
| 429 // See if there is at least one marked node in this root's chain. | |
| 430 for (const Node* node = &root; ; node = &all_nodes_[node->child]) { | |
| 431 if ((node->flags & flag) != 0) { | |
| 432 total_weight += static_cast<uint64>(root.depends_on.priority); | |
| 433 roots[total_weight] = root_id; | |
| 434 break; | |
| 435 } | |
| 436 if (node->child == NodeId()) { | |
| 437 break; | |
| 438 } | |
| 439 DCHECK(NodeExists(node->child)); | |
| 440 } | |
| 441 } | |
| 442 } | |
| 443 | |
| 444 // If there are no ready nodes, then return NodeId(). | |
| 445 if (total_weight == 0) { | |
| 446 DCHECK(roots.empty()); | |
| 447 return NodeId(); | |
| 448 } else { | |
| 449 DCHECK(!roots.empty()); | |
| 450 } | |
| 451 | |
| 452 // Randomly select a tree to use. | |
| 453 typename std::map<uint64, NodeId>::const_iterator root_iter = | |
| 454 roots.upper_bound(base::RandGenerator(total_weight)); | |
| 455 DCHECK(root_iter != roots.end()); | |
| 456 const NodeId root_id = root_iter->second; | |
| 457 | |
| 458 // Find the first node in the chain that is ready. | |
| 459 NodeId node_id = root_id; | |
| 460 while (true) { | |
| 461 DCHECK(NodeExists(node_id)); | |
| 462 Node* node = &all_nodes_[node_id]; | |
| 463 if ((node->flags & flag) != 0) { | |
| 464 // There might be more nodes that are ready and that are linked to this | |
| 465 // one in an unordered chain. Find all of them, then pick one randomly. | |
| 466 std::vector<NodeId> group; | |
| 467 group.push_back(node_id); | |
| 468 for (Node* next = node; next->child != NodeId();) { | |
| 469 DCHECK(NodeExists(next->child)); | |
| 470 Node *child = &all_nodes_[next->child]; | |
| 471 DCHECK_NE(ROOT_NODE, child->type); | |
| 472 if (child->type != NONROOT_UNORDERED) { | |
| 473 break; | |
| 474 } | |
| 475 if ((child->flags & flag) != 0) { | |
| 476 group.push_back(next->child); | |
| 477 } | |
| 478 next = child; | |
| 479 } | |
| 480 return group[base::RandGenerator(group.size())]; | |
| 481 } | |
| 482 node_id = node->child; | |
| 483 } | |
| 484 } | |
| 485 | |
| 486 template <typename NodeId, typename Priority> | |
| 487 const typename SpdyPriorityForest<NodeId, Priority>::Node* | |
| 488 SpdyPriorityForest<NodeId, Priority>::FindNode(NodeId node_id) const { | |
| 489 typename NodeMap::const_iterator iter = all_nodes_.find(node_id); | |
| 490 if (iter == all_nodes_.end()) { | |
| 491 return NULL; | |
| 492 } | |
| 493 return &iter->second; | |
| 494 } | |
| 495 | |
| 496 template <typename NodeId, typename Priority> | |
| 497 bool SpdyPriorityForest<NodeId, Priority>::ValidateInvariantsForTests() const { | |
| 498 for (typename NodeMap::const_iterator iter = all_nodes_.begin(); | |
| 499 iter != all_nodes_.end(); ++iter) { | |
| 500 const NodeId node_id = iter->first; | |
| 501 const Node& node = iter->second; | |
| 502 if (node.type != ROOT_NODE && | |
| 503 (!NodeExists(node.depends_on.parent_id) || | |
| 504 GetChild(node.depends_on.parent_id) != node_id)) { | |
| 505 return false; | |
| 506 } | |
| 507 if (node.child != NodeId()) { | |
| 508 if (!NodeExists(node.child) || node_id != GetParent(node.child)) { | |
| 509 return false; | |
| 510 } | |
| 511 } | |
| 512 | |
| 513 NodeId child_id = node.child; | |
| 514 int count = 0; | |
| 515 while (child_id != NodeId()) { | |
| 516 if (count > num_nodes() || node_id == child_id) { | |
| 517 return false; | |
| 518 } | |
| 519 child_id = GetChild(child_id); | |
| 520 ++count; | |
| 521 } | |
| 522 } | |
| 523 return true; | |
| 524 } | |
| 525 | |
| 526 } // namespace net | |
| 527 | |
| 528 #endif // NET_SPDY_SPDY_PRIORITY_FOREST_H_ | |
| OLD | NEW |