1 /** @file
2
3 HTTP/2 Dependency Tree
4
5 The original idea of Stream Priority Algorithm using Weighted Fair Queue (WFQ)
6 Scheduling is invented by Kazuho Oku (H2O project).
7
8 @section license License
9
10 Licensed to the Apache Software Foundation (ASF) under one
11 or more contributor license agreements. See the NOTICE file
12 distributed with this work for additional information
13 regarding copyright ownership. The ASF licenses this file
14 to you under the Apache License, Version 2.0 (the
15 "License"); you may not use this file except in compliance
16 with the License. You may obtain a copy of the License at
17
18 http://www.apache.org/licenses/LICENSE-2.0
19
20 Unless required by applicable law or agreed to in writing, software
21 distributed under the License is distributed on an "AS IS" BASIS,
22 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23 See the License for the specific language governing permissions and
24 limitations under the License.
25 */
26
27 #pragma once
28
29 #include "tscore/List.h"
30 #include "tscore/Diags.h"
31 #include "tscore/PriorityQueue.h"
32
33 #include "HTTP2.h"
34
35 // TODO: K is a constant, 256 is temporal value.
36 const static uint32_t K = 256;
37 const static uint32_t HTTP2_DEPENDENCY_TREE_MAX_DEPTH = 256;
38
39 namespace Http2DependencyTree
40 {
41 class Node
42 {
43 public:
t(t)44 explicit Node(void *t = nullptr) : t(t)
45 {
46 entry = new PriorityQueueEntry<Node *>(this);
47 queue = new PriorityQueue<Node *>();
48 }
49
id(i)50 Node(uint32_t i, uint32_t w, uint32_t p, Node *n, void *t = nullptr) : id(i), weight(w), point(p), t(t), parent(n)
51 {
52 entry = new PriorityQueueEntry<Node *>(this);
53 queue = new PriorityQueue<Node *>();
54 }
55
~Node()56 ~Node()
57 {
58 delete entry;
59 delete queue;
60
61 // delete all child nodes
62 if (!children.empty()) {
63 Node *node = children.head;
64 Node *next = nullptr;
65 while (node) {
66 next = node->link.next;
67 children.remove(node);
68 delete node;
69 node = next;
70 }
71 }
72 }
73
74 Node(const Node &) = delete;
75 Node &operator=(const Node &) = delete;
76
77 LINK(Node, link);
78
79 bool
80 operator<(const Node &n) const
81 {
82 return point < n.point;
83 }
84
85 bool
86 operator>(const Node &n) const
87 {
88 return point > n.point;
89 }
90
91 /**
92 * Added an explicit shadow flag. The original logic
93 * was using an null Http2Stream frame to mark a shadow node
94 * but that would pull in Priority holder nodes too
95 */
96 bool
is_shadow()97 is_shadow() const
98 {
99 return shadow == true;
100 }
101
102 bool active = false;
103 bool queued = false;
104 bool shadow = false;
105 uint32_t id = HTTP2_PRIORITY_DEFAULT_STREAM_DEPENDENCY;
106 uint32_t weight = HTTP2_PRIORITY_DEFAULT_WEIGHT;
107 uint32_t point = 0;
108 void *t = nullptr;
109 Node *parent = nullptr;
110 DLL<Node> children;
111 PriorityQueueEntry<Node *> *entry;
112 PriorityQueue<Node *> *queue;
113 };
114
115 template <typename T> class Tree
116 {
117 public:
Tree(uint32_t max_concurrent_streams)118 explicit Tree(uint32_t max_concurrent_streams) : _max_depth(MIN(max_concurrent_streams, HTTP2_DEPENDENCY_TREE_MAX_DEPTH))
119 {
120 _ancestors.resize(_max_ancestors);
121 }
~Tree()122 ~Tree() { delete _root; }
123 Node *find(uint32_t id, bool *is_max_leaf = nullptr);
124 Node *find_shadow(uint32_t id, bool *is_max_leaf = nullptr);
125 Node *add(uint32_t parent_id, uint32_t id, uint32_t weight, bool exclusive, T t, bool shadow = false);
126 Node *reprioritize(uint32_t id, uint32_t new_parent_id, bool exclusive);
127 Node *reprioritize(Node *node, uint32_t id, bool exclusive);
128 Node *top();
129 void remove(Node *node);
130 void activate(Node *node);
131 void deactivate(Node *node, uint32_t sent);
132 void update(Node *node, uint32_t sent);
133 bool in(Node *current, Node *node);
134 uint32_t size() const;
135 /*
136 * Dump the priority tree relationships in JSON form for debugging
137 */
138 void
dump_tree(std::ostream & output)139 dump_tree(std::ostream &output) const
140 {
141 _dump(_root, output);
142 }
143 void add_ancestor(Node *node);
144 uint32_t was_ancestor(uint32_t id) const;
145
146 private:
147 void _dump(Node *node, std::ostream &output) const;
148 Node *_find(Node *node, uint32_t id, uint32_t depth = 1, bool *is_max_leaf = nullptr);
149 Node *_top(Node *node);
150 void _change_parent(Node *node, Node *new_parent, bool exclusive);
151 bool in_parent_chain(Node *maybe_parent, Node *target);
152
153 Node *_root = new Node(this);
154 uint32_t _max_depth;
155 uint32_t _node_count = 0;
156 /*
157 * _ancestors in a circular buffer tracking parent relationships for
158 * recently completed nodes. Without this new streams may not find their
159 * parents and be inserted at the root, violating the client's desired
160 * dependency relationship. This addresses the issue identified in section
161 * 5.3.4 of the HTTP/2 spec
162 *
163 * "It is possible for a stream to become closed while prioritization
164 * information that creates a dependency on that stream is in transit.
165 * If a stream identified in a dependency has no associated priority
166 * information, then the dependent stream is instead assigned a default
167 * priority (Section 5.3.5). This potentially creates suboptimal
168 * prioritization, since the stream could be given a priority that is
169 * different from what is intended.
170 * To avoid these problems, an endpoint SHOULD retain stream
171 * prioritization state for a period after streams become closed. The
172 * longer state is retained, the lower the chance that streams are
173 * assigned incorrect or default priority values."
174 */
175 static const uint32_t _max_ancestors = 64;
176 uint32_t _ancestor_index = 0;
177 std::vector<std::pair<uint32_t, uint32_t>> _ancestors;
178 };
179
180 template <typename T>
181 void
_dump(Node * node,std::ostream & output)182 Tree<T>::_dump(Node *node, std::ostream &output) const
183 {
184 output << R"({ "id":")" << node->id << "/" << node->weight << "/" << node->point << "/" << ((node->t != nullptr) ? "1" : "0")
185 << "/" << ((node->active) ? "a" : "d") << "\",";
186 // Dump the children
187 output << " \"c\":[";
188 for (Node *n = node->children.head; n; n = n->link.next) {
189 _dump(n, output);
190 output << ",";
191 }
192 output << "] }";
193 }
194
195 template <typename T>
196 Node *
_find(Node * node,uint32_t id,uint32_t depth,bool * is_max_leaf)197 Tree<T>::_find(Node *node, uint32_t id, uint32_t depth, bool *is_max_leaf)
198 {
199 if (node->id == id) {
200 if (is_max_leaf) {
201 *is_max_leaf = depth == _max_depth;
202 }
203 return node;
204 }
205
206 if (node->children.empty() || depth > _max_depth) {
207 return nullptr;
208 }
209
210 Node *result = nullptr;
211 for (Node *n = node->children.head; n; n = n->link.next) {
212 result = _find(n, id, ++depth, is_max_leaf);
213 if (result != nullptr) {
214 break;
215 }
216 }
217
218 return result;
219 }
220
221 template <typename T>
222 Node *
223 Tree<T>::find_shadow(uint32_t id, bool *is_max_leaf)
224 {
225 return _find(_root, id, 1, is_max_leaf);
226 }
227
228 template <typename T>
229 Node *
230 Tree<T>::find(uint32_t id, bool *is_max_leaf)
231 {
232 Node *n = _find(_root, id, 1, is_max_leaf);
233 return n == nullptr ? nullptr : (n->is_shadow() ? nullptr : n);
234 }
235
236 template <typename T>
237 void
238 Tree<T>::add_ancestor(Node *node)
239 {
240 if (node->parent != _root) {
241 _ancestors[_ancestor_index].first = node->id;
242 _ancestors[_ancestor_index].second = node->parent->id;
243 _ancestor_index++;
244 if (_ancestor_index >= _max_ancestors) {
245 _ancestor_index = 0;
246 }
247 }
248 }
249
250 template <typename T>
251 uint32_t
252 Tree<T>::was_ancestor(uint32_t pid) const
253 {
254 uint32_t i = (_ancestor_index == 0) ? _max_ancestors - 1 : _ancestor_index - 1;
255 while (i != _ancestor_index) {
256 if (_ancestors[i].first == pid) {
257 return _ancestors[i].second;
258 }
259 i = (i == 0) ? _max_ancestors - 1 : i - 1;
260 }
261 return 0;
262 }
263
264 template <typename T>
265 Node *
266 Tree<T>::add(uint32_t parent_id, uint32_t id, uint32_t weight, bool exclusive, T t, bool shadow)
267 {
268 // Can we vivify a shadow node?
269 Node *node = find_shadow(id);
270 if (node != nullptr && node->is_shadow()) {
271 node->t = t;
272 node->point = id;
273 node->weight = weight;
274 node->shadow = false;
275 // Move the shadow node into the proper position in the tree
276 node = reprioritize(node, parent_id, exclusive);
277 return node;
278 }
279
280 bool is_max_leaf = false;
281 Node *parent = find_shadow(parent_id, &is_max_leaf); // Look for real and shadow nodes
282
283 if (parent == nullptr) {
284 if (parent_id < id) { // See if we still have a history of the parent
285 uint32_t pid = parent_id;
286 do {
287 pid = was_ancestor(pid);
288 if (pid != 0) {
289 parent = find(pid);
290 }
291 } while (pid != 0 && parent == nullptr);
292 if (parent == nullptr) {
293 // Found no ancestor, just add to root at default weight
294 weight = HTTP2_PRIORITY_DEFAULT_WEIGHT;
295 exclusive = false;
296 parent = _root;
297 }
298 }
299 if (parent == nullptr || parent == _root) { // Create a shadow node
300 parent = add(0, parent_id, HTTP2_PRIORITY_DEFAULT_WEIGHT, false, nullptr, true);
301 exclusive = false;
302 }
303 } else if (is_max_leaf) { // Chain too long, just add to root
304 parent = _root;
305 exclusive = false;
306 }
307
308 // Use stream id as initial point
309 node = new Node(id, weight, id, parent, t);
310
311 if (exclusive) {
312 while (Node *child = parent->children.pop()) {
313 if (child->queued) {
314 parent->queue->erase(child->entry);
315 node->queue->push(child->entry);
316 }
317 node->children.push(child);
318 child->parent = node;
319 }
320 }
321
322 parent->children.push(node);
323 if (!node->queue->empty()) {
324 ink_release_assert(!node->queued);
325 parent->queue->push(node->entry);
326 node->queued = true;
327 }
328 node->shadow = shadow;
329 ++_node_count;
330 return node;
331 }
332
333 template <typename T>
334 bool
335 Tree<T>::in(Node *current, Node *node)
336 {
337 bool retval = false;
338 if (current == nullptr)
339 current = _root;
340 if (current->queue->in(node->entry)) {
341 return true;
342 } else {
343 Node *child = current->children.head;
344 while (child) {
345 if (in(child, node)) {
346 return true;
347 }
348 child = child->link.next;
349 }
350 }
351 return retval;
352 }
353
354 template <typename T>
355 void
356 Tree<T>::remove(Node *node)
357 {
358 if (node == _root || node->active) {
359 return;
360 }
361
362 // Make a note of node's ancestry
363 add_ancestor(node);
364
365 Node *parent = node->parent;
366 parent->children.remove(node);
367 if (node->queued) {
368 parent->queue->erase(node->entry);
369 }
370
371 // Push queue entries
372 while (!node->queue->empty()) {
373 parent->queue->push(node->queue->top());
374 node->queue->pop();
375 }
376
377 // Push children
378 while (!node->children.empty()) {
379 Node *child = node->children.pop();
380 parent->children.push(child);
381 child->parent = parent;
382 }
383
384 // delete the shadow parent
385 if (parent->is_shadow() && parent->children.empty() && parent->queue->empty()) {
386 remove(parent);
387 }
388
389 // ink_release_assert(!this->in(nullptr, node));
390
391 --_node_count;
392 delete node;
393 }
394
395 template <typename T>
396 Node *
397 Tree<T>::reprioritize(uint32_t id, uint32_t new_parent_id, bool exclusive)
398 {
399 Node *node = find(id);
400 if (node == nullptr) {
401 return node;
402 }
403
404 return reprioritize(node, new_parent_id, exclusive);
405 }
406
407 template <typename T>
408 Node *
409 Tree<T>::reprioritize(Node *node, uint32_t new_parent_id, bool exclusive)
410 {
411 if (node == nullptr) {
412 return node;
413 }
414
415 Node *old_parent = node->parent;
416 if (old_parent->id == new_parent_id) {
417 // Do nothing
418 return node;
419 }
420 // should not change the root node
421 ink_assert(node->parent);
422
423 Node *new_parent = find(new_parent_id);
424 if (new_parent == nullptr) {
425 return node;
426 }
427 // If node is dependent on the new parent, must move the new parent first
428 if (new_parent_id != 0 && in_parent_chain(node, new_parent)) {
429 _change_parent(new_parent, old_parent, false);
430 }
431 _change_parent(node, new_parent, exclusive);
432
433 // delete the shadow node
434 if (node->is_shadow() && node->children.empty() && node->queue->empty()) {
435 remove(node);
436 return nullptr;
437 }
438
439 return node;
440 }
441
442 template <typename T>
443 bool
444 Tree<T>::in_parent_chain(Node *maybe_parent, Node *target)
445 {
446 bool retval = false;
447 Node *parent = target->parent;
448 while (parent != nullptr && !retval) {
449 retval = maybe_parent == parent;
450 parent = parent->parent;
451 }
452 return retval;
453 }
454
455 // Change node's parent to new_parent
456 template <typename T>
457 void
458 Tree<T>::_change_parent(Node *node, Node *new_parent, bool exclusive)
459 {
460 ink_release_assert(node->parent != nullptr);
461 node->parent->children.remove(node);
462 if (node->queued) {
463 node->parent->queue->erase(node->entry);
464 node->queued = false;
465
466 Node *current = node->parent;
467 while (current->queue->empty() && !current->active && current->parent != nullptr) {
468 current->parent->queue->erase(current->entry);
469 current->queued = false;
470 current = current->parent;
471 }
472 }
473
474 node->parent = nullptr;
475 if (exclusive) {
476 while (Node *child = new_parent->children.pop()) {
477 if (child->queued) {
478 child->parent->queue->erase(child->entry);
479 node->queue->push(child->entry);
480 }
481
482 node->children.push(child);
483 ink_release_assert(child != node);
484 child->parent = node;
485 }
486 }
487
488 new_parent->children.push(node);
489 ink_release_assert(node != new_parent);
490 node->parent = new_parent;
491
492 if (node->active || !node->queue->empty()) {
493 Node *current = node;
494 while (current->parent != nullptr && !current->queued) {
495 current->parent->queue->push(current->entry);
496 current->queued = true;
497 current = current->parent;
498 }
499 }
500 }
501
502 template <typename T>
503 Node *
504 Tree<T>::_top(Node *node)
505 {
506 Node *child = node;
507
508 while (child != nullptr) {
509 if (child->active) {
510 return child;
511 } else if (!child->queue->empty()) {
512 child = child->queue->top()->node;
513 } else {
514 return nullptr;
515 }
516 }
517
518 return child;
519 }
520
521 template <typename T>
522 Node *
523 Tree<T>::top()
524 {
525 return _top(_root);
526 }
527
528 template <typename T>
529 void
530 Tree<T>::activate(Node *node)
531 {
532 node->active = true;
533
534 while (node->parent != nullptr && !node->queued) {
535 node->parent->queue->push(node->entry);
536 node->queued = true;
537 node = node->parent;
538 }
539 }
540
541 template <typename T>
542 void
543 Tree<T>::deactivate(Node *node, uint32_t sent)
544 {
545 node->active = false;
546
547 while (!node->active && node->queue->empty() && node->parent != nullptr) {
548 if (node->queued) {
549 node->parent->queue->erase(node->entry);
550 node->queued = false;
551 }
552
553 node = node->parent;
554 }
555
556 update(node, sent);
557 }
558
559 template <typename T>
560 void
561 Tree<T>::update(Node *node, uint32_t sent)
562 {
563 while (node->parent != nullptr) {
564 node->point += sent * K / (node->weight + 1);
565
566 if (node->queued) {
567 node->parent->queue->update(node->entry, true);
568 } else {
569 node->parent->queue->push(node->entry);
570 node->queued = true;
571 }
572
573 node = node->parent;
574 }
575 }
576
577 template <typename T>
578 uint32_t
579 Tree<T>::size() const
580 {
581 return _node_count;
582 }
583 } // namespace Http2DependencyTree
584