| /** @file | |
| HTTP/2 Dependency Tree | |
| The original idea of Stream Priority Algorithm using Weighted Fair Queue (WFQ) | |
| Scheduling is invented by Kazuho Oku (H2O project). | |
| @section license License | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #pragma once | |
| #include <memory> | |
| #include "tscore/List.h" | |
| #include "tscore/Diags.h" | |
| #include "tscore/PriorityQueue.h" | |
| #include "proxy/http2/HTTP2.h" | |
| // TODO: K is a constant, 256 is temporal value. | |
| const static uint32_t K = 256; | |
| const static uint32_t HTTP2_DEPENDENCY_TREE_MAX_DEPTH = 256; | |
| namespace Http2DependencyTree | |
| { | |
| class Node | |
| { | |
| public: | |
| explicit Node(void *t = nullptr) : t(t) | |
| { | |
| entry = std::make_unique<PriorityQueueEntry<Node *>>(this); | |
| queue = std::make_unique<PriorityQueue<Node *>>(); | |
| } | |
| 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) | |
| { | |
| entry = std::make_unique<PriorityQueueEntry<Node *>>(this); | |
| queue = std::make_unique<PriorityQueue<Node *>>(); | |
| } | |
| ~Node() | |
| { | |
| // delete all child nodes | |
| if (!children.empty()) { | |
| Node *node = children.head; | |
| Node *next = nullptr; | |
| while (node) { | |
| next = node->link.next; | |
| children.remove(node); | |
| delete node; | |
| node = next; | |
| } | |
| } | |
| } | |
| Node(const Node &) = delete; | |
| Node &operator=(const Node &) = delete; | |
| LINK(Node, link); | |
| bool | |
| operator<(const Node &n) const | |
| { | |
| return point < n.point; | |
| } | |
| bool | |
| operator>(const Node &n) const | |
| { | |
| return point > n.point; | |
| } | |
| /** | |
| * Added an explicit shadow flag. The original logic | |
| * was using an null Http2Stream frame to mark a shadow node | |
| * but that would pull in Priority holder nodes too | |
| */ | |
| bool | |
| is_shadow() const | |
| { | |
| return shadow == true; | |
| } | |
| bool active = false; | |
| bool queued = false; | |
| bool shadow = false; | |
| uint32_t id = HTTP2_PRIORITY_DEFAULT_STREAM_DEPENDENCY; | |
| uint32_t weight = HTTP2_PRIORITY_DEFAULT_WEIGHT; | |
| uint32_t point = 0; | |
| void *t = nullptr; | |
| Node *parent = nullptr; | |
| DLL<Node> children; | |
| std::unique_ptr<PriorityQueueEntry<Node *>> entry; | |
| std::unique_ptr<PriorityQueue<Node *>> queue; | |
| }; | |
| template <typename T> class Tree | |
| { | |
| public: | |
| explicit Tree(uint32_t max_concurrent_streams) : _max_depth(MIN(max_concurrent_streams, HTTP2_DEPENDENCY_TREE_MAX_DEPTH)) | |
| { | |
| _ancestors.resize(_max_ancestors); | |
| } | |
| ~Tree() { delete _root; } | |
| Node *find(uint32_t id, bool *is_max_leaf = nullptr); | |
| Node *find_shadow(uint32_t id, bool *is_max_leaf = nullptr); | |
| Node *add(uint32_t parent_id, uint32_t id, uint32_t weight, bool exclusive, T t, bool shadow = false); | |
| Node *reprioritize(uint32_t id, uint32_t new_parent_id, bool exclusive); | |
| Node *reprioritize(Node *node, uint32_t id, bool exclusive); | |
| Node *top(); | |
| void remove(Node *node); | |
| void activate(Node *node); | |
| void deactivate(Node *node, uint32_t sent); | |
| void update(Node *node, uint32_t sent); | |
| bool in(Node *current, Node *node); | |
| uint32_t size() const; | |
| /* | |
| * Dump the priority tree relationships in JSON form for debugging | |
| */ | |
| void | |
| dump_tree(std::ostream &output) const | |
| { | |
| _dump(_root, output); | |
| } | |
| void add_ancestor(Node *node); | |
| uint32_t was_ancestor(uint32_t id) const; | |
| private: | |
| void _dump(Node *node, std::ostream &output) const; | |
| Node *_find(Node *node, uint32_t id, uint32_t depth = 1, bool *is_max_leaf = nullptr); | |
| Node *_top(Node *node); | |
| void _change_parent(Node *node, Node *new_parent, bool exclusive); | |
| bool in_parent_chain(Node *maybe_parent, Node *target); | |
| Node *_root = new Node(this); | |
| uint32_t _max_depth; | |
| uint32_t _node_count = 0; | |
| /* | |
| * _ancestors in a circular buffer tracking parent relationships for | |
| * recently completed nodes. Without this new streams may not find their | |
| * parents and be inserted at the root, violating the client's desired | |
| * dependency relationship. This addresses the issue identified in section | |
| * 5.3.4 of the HTTP/2 spec | |
| * | |
| * "It is possible for a stream to become closed while prioritization | |
| * information that creates a dependency on that stream is in transit. | |
| * If a stream identified in a dependency has no associated priority | |
| * information, then the dependent stream is instead assigned a default | |
| * priority (Section 5.3.5). This potentially creates suboptimal | |
| * prioritization, since the stream could be given a priority that is | |
| * different from what is intended. | |
| * To avoid these problems, an endpoint SHOULD retain stream | |
| * prioritization state for a period after streams become closed. The | |
| * longer state is retained, the lower the chance that streams are | |
| * assigned incorrect or default priority values." | |
| */ | |
| static const uint32_t _max_ancestors = 64; | |
| uint32_t _ancestor_index = 0; | |
| std::vector<std::pair<uint32_t, uint32_t>> _ancestors; | |
| }; | |
| template <typename T> | |
| void | |
| Tree<T>::_dump(Node *node, std::ostream &output) const | |
| { | |
| output << R"({ "id":")" << node->id << "/" << node->weight << "/" << node->point << "/" << ((node->t != nullptr) ? "1" : "0") | |
| << "/" << ((node->active) ? "a" : "d") << "\","; | |
| // Dump the children | |
| output << " \"c\":["; | |
| for (Node *n = node->children.head; n; n = n->link.next) { | |
| _dump(n, output); | |
| output << ","; | |
| } | |
| output << "] }"; | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::_find(Node *node, uint32_t id, uint32_t depth, bool *is_max_leaf) | |
| { | |
| if (node->id == id) { | |
| if (is_max_leaf) { | |
| *is_max_leaf = depth == _max_depth; | |
| } | |
| return node; | |
| } | |
| if (node->children.empty() || depth > _max_depth) { | |
| return nullptr; | |
| } | |
| Node *result = nullptr; | |
| for (Node *n = node->children.head; n; n = n->link.next) { | |
| result = _find(n, id, ++depth, is_max_leaf); | |
| if (result != nullptr) { | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::find_shadow(uint32_t id, bool *is_max_leaf) | |
| { | |
| return _find(_root, id, 1, is_max_leaf); | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::find(uint32_t id, bool *is_max_leaf) | |
| { | |
| Node *n = _find(_root, id, 1, is_max_leaf); | |
| return n == nullptr ? nullptr : (n->is_shadow() ? nullptr : n); | |
| } | |
| template <typename T> | |
| void | |
| Tree<T>::add_ancestor(Node *node) | |
| { | |
| if (node->parent != _root) { | |
| _ancestors[_ancestor_index].first = node->id; | |
| _ancestors[_ancestor_index].second = node->parent->id; | |
| _ancestor_index++; | |
| if (_ancestor_index >= _max_ancestors) { | |
| _ancestor_index = 0; | |
| } | |
| } | |
| } | |
| template <typename T> | |
| uint32_t | |
| Tree<T>::was_ancestor(uint32_t pid) const | |
| { | |
| uint32_t i = (_ancestor_index == 0) ? _max_ancestors - 1 : _ancestor_index - 1; | |
| while (i != _ancestor_index) { | |
| if (_ancestors[i].first == pid) { | |
| return _ancestors[i].second; | |
| } | |
| i = (i == 0) ? _max_ancestors - 1 : i - 1; | |
| } | |
| return 0; | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::add(uint32_t parent_id, uint32_t id, uint32_t weight, bool exclusive, T t, bool shadow) | |
| { | |
| // Can we vivify a shadow node? | |
| Node *node = find_shadow(id); | |
| if (node != nullptr && node->is_shadow()) { | |
| node->t = t; | |
| node->point = id; | |
| node->weight = weight; | |
| node->shadow = false; | |
| // Move the shadow node into the proper position in the tree | |
| node = reprioritize(node, parent_id, exclusive); | |
| return node; | |
| } | |
| bool is_max_leaf = false; | |
| Node *parent = find_shadow(parent_id, &is_max_leaf); // Look for real and shadow nodes | |
| if (parent == nullptr) { | |
| if (parent_id < id) { // See if we still have a history of the parent | |
| uint32_t pid = parent_id; | |
| do { | |
| pid = was_ancestor(pid); | |
| if (pid != 0) { | |
| parent = find(pid); | |
| } | |
| } while (pid != 0 && parent == nullptr); | |
| if (parent == nullptr) { | |
| // Found no ancestor, just add to root at default weight | |
| weight = HTTP2_PRIORITY_DEFAULT_WEIGHT; | |
| exclusive = false; | |
| parent = _root; | |
| } | |
| } | |
| if (parent == nullptr || parent == _root) { // Create a shadow node | |
| parent = add(0, parent_id, HTTP2_PRIORITY_DEFAULT_WEIGHT, false, nullptr, true); | |
| exclusive = false; | |
| } | |
| } else if (is_max_leaf) { // Chain too long, just add to root | |
| parent = _root; | |
| exclusive = false; | |
| } | |
| // Use stream id as initial point | |
| node = new Node(id, weight, id, parent, t); | |
| if (exclusive) { | |
| while (Node *child = parent->children.pop()) { | |
| if (child->queued) { | |
| parent->queue->erase(child->entry.get()); | |
| node->queue->push(child->entry.get()); | |
| } | |
| node->children.push(child); | |
| child->parent = node; | |
| } | |
| } | |
| parent->children.push(node); | |
| if (!node->queue->empty()) { | |
| ink_release_assert(!node->queued); | |
| parent->queue->push(node->entry.get()); | |
| node->queued = true; | |
| } | |
| node->shadow = shadow; | |
| ++_node_count; | |
| return node; | |
| } | |
| template <typename T> | |
| bool | |
| Tree<T>::in(Node *current, Node *node) | |
| { | |
| bool retval = false; | |
| if (current == nullptr) | |
| current = _root; | |
| if (current->queue->in(node->entry.get())) { | |
| return true; | |
| } else { | |
| Node *child = current->children.head; | |
| while (child) { | |
| if (in(child, node)) { | |
| return true; | |
| } | |
| child = child->link.next; | |
| } | |
| } | |
| return retval; | |
| } | |
| template <typename T> | |
| void | |
| Tree<T>::remove(Node *node) | |
| { | |
| if (node == _root || node->active) { | |
| return; | |
| } | |
| // Make a note of node's ancestry | |
| add_ancestor(node); | |
| Node *parent = node->parent; | |
| parent->children.remove(node); | |
| if (node->queued) { | |
| parent->queue->erase(node->entry.get()); | |
| } | |
| // Push queue entries | |
| while (!node->queue->empty()) { | |
| parent->queue->push(node->queue->top()); | |
| node->queue->pop(); | |
| } | |
| // Push children | |
| while (!node->children.empty()) { | |
| Node *child = node->children.pop(); | |
| parent->children.push(child); | |
| child->parent = parent; | |
| } | |
| // delete the shadow parent | |
| if (parent->is_shadow() && parent->children.empty() && parent->queue->empty()) { | |
| remove(parent); | |
| } | |
| // ink_release_assert(!this->in(nullptr, node)); | |
| --_node_count; | |
| delete node; | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::reprioritize(uint32_t id, uint32_t new_parent_id, bool exclusive) | |
| { | |
| Node *node = find(id); | |
| if (node == nullptr) { | |
| return node; | |
| } | |
| return reprioritize(node, new_parent_id, exclusive); | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::reprioritize(Node *node, uint32_t new_parent_id, bool exclusive) | |
| { | |
| if (node == nullptr) { | |
| return node; | |
| } | |
| Node *old_parent = node->parent; | |
| if (old_parent->id == new_parent_id) { | |
| // Do nothing | |
| return node; | |
| } | |
| // should not change the root node | |
| ink_assert(node->parent); | |
| Node *new_parent = find(new_parent_id); | |
| if (new_parent == nullptr) { | |
| return node; | |
| } | |
| // If node is dependent on the new parent, must move the new parent first | |
| if (new_parent_id != 0 && in_parent_chain(node, new_parent)) { | |
| _change_parent(new_parent, old_parent, false); | |
| } | |
| _change_parent(node, new_parent, exclusive); | |
| // delete the shadow node | |
| if (node->is_shadow() && node->children.empty() && node->queue->empty()) { | |
| remove(node); | |
| return nullptr; | |
| } | |
| return node; | |
| } | |
| template <typename T> | |
| bool | |
| Tree<T>::in_parent_chain(Node *maybe_parent, Node *target) | |
| { | |
| bool retval = false; | |
| Node *parent = target->parent; | |
| while (parent != nullptr && !retval) { | |
| retval = maybe_parent == parent; | |
| parent = parent->parent; | |
| } | |
| return retval; | |
| } | |
| // Change node's parent to new_parent | |
| template <typename T> | |
| void | |
| Tree<T>::_change_parent(Node *node, Node *new_parent, bool exclusive) | |
| { | |
| ink_release_assert(node->parent != nullptr); | |
| node->parent->children.remove(node); | |
| if (node->queued) { | |
| node->parent->queue->erase(node->entry.get()); | |
| node->queued = false; | |
| Node *current = node->parent; | |
| while (current->queue->empty() && !current->active && current->parent != nullptr) { | |
| current->parent->queue->erase(current->entry.get()); | |
| current->queued = false; | |
| current = current->parent; | |
| } | |
| } | |
| node->parent = nullptr; | |
| if (exclusive) { | |
| while (Node *child = new_parent->children.pop()) { | |
| if (child->queued) { | |
| child->parent->queue->erase(child->entry.get()); | |
| node->queue->push(child->entry.get()); | |
| } | |
| node->children.push(child); | |
| ink_release_assert(child != node); | |
| child->parent = node; | |
| } | |
| } | |
| new_parent->children.push(node); | |
| ink_release_assert(node != new_parent); | |
| node->parent = new_parent; | |
| if (node->active || !node->queue->empty()) { | |
| Node *current = node; | |
| while (current->parent != nullptr && !current->queued) { | |
| current->parent->queue->push(current->entry.get()); | |
| current->queued = true; | |
| current = current->parent; | |
| } | |
| } | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::_top(Node *node) | |
| { | |
| Node *child = node; | |
| while (child != nullptr) { | |
| if (child->active) { | |
| return child; | |
| } else if (!child->queue->empty()) { | |
| child = child->queue->top()->node; | |
| } else { | |
| return nullptr; | |
| } | |
| } | |
| return child; | |
| } | |
| template <typename T> | |
| Node * | |
| Tree<T>::top() | |
| { | |
| return _top(_root); | |
| } | |
| template <typename T> | |
| void | |
| Tree<T>::activate(Node *node) | |
| { | |
| node->active = true; | |
| while (node->parent != nullptr && !node->queued) { | |
| node->parent->queue->push(node->entry.get()); | |
| node->queued = true; | |
| node = node->parent; | |
| } | |
| } | |
| template <typename T> | |
| void | |
| Tree<T>::deactivate(Node *node, uint32_t sent) | |
| { | |
| node->active = false; | |
| while (!node->active && node->queue->empty() && node->parent != nullptr) { | |
| if (node->queued) { | |
| node->parent->queue->erase(node->entry.get()); | |
| node->queued = false; | |
| } | |
| node = node->parent; | |
| } | |
| update(node, sent); | |
| } | |
| template <typename T> | |
| void | |
| Tree<T>::update(Node *node, uint32_t sent) | |
| { | |
| while (node->parent != nullptr) { | |
| node->point += sent * K / (node->weight + 1); | |
| if (node->queued) { | |
| node->parent->queue->update(node->entry.get(), true); | |
| } else { | |
| node->parent->queue->push(node->entry.get()); | |
| node->queued = true; | |
| } | |
| node = node->parent; | |
| } | |
| } | |
| template <typename T> | |
| uint32_t | |
| Tree<T>::size() const | |
| { | |
| return _node_count; | |
| } | |
| } // namespace Http2DependencyTree |