From 015374e69db2a8d37d3c8f88a5c871329e1b81e8 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 4 Dec 2024 08:03:01 +0100 Subject: [PATCH] DependencyGraph: Allow lookups by parent & child dependencies --- lib/base/dependencygraph.cpp | 68 +++++++++++++++++++++----------- lib/base/dependencygraph.hpp | 75 +++++++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 24 deletions(-) diff --git a/lib/base/dependencygraph.cpp b/lib/base/dependencygraph.cpp index 58f685ce0..a9d0dbb95 100644 --- a/lib/base/dependencygraph.cpp +++ b/lib/base/dependencygraph.cpp @@ -5,45 +5,69 @@ using namespace icinga; std::mutex DependencyGraph::m_Mutex; -std::map> DependencyGraph::m_Dependencies; +DependencyGraph::DependencyMap DependencyGraph::m_Dependencies; void DependencyGraph::AddDependency(ConfigObject* child, ConfigObject* parent) { std::unique_lock lock(m_Mutex); - m_Dependencies[parent][child]++; + if (auto [it, inserted] = m_Dependencies.insert(Edge(parent, child)); !inserted) { + m_Dependencies.modify(it, [](Edge& e) { e.count++; }); + } } void DependencyGraph::RemoveDependency(ConfigObject* child, ConfigObject* parent) { std::unique_lock lock(m_Mutex); - auto& refs = m_Dependencies[parent]; - auto it = refs.find(child); + if (auto it(m_Dependencies.find(Edge(parent, child))); it != m_Dependencies.end()) { + // A number <= 1 means, this isn't referenced by anyone and should be erased from the container. + if (it->count == 1) { + m_Dependencies.erase(it); + return; + } - if (it == refs.end()) - return; - - it->second--; - - if (it->second == 0) - refs.erase(it); - - if (refs.empty()) - m_Dependencies.erase(parent); + // Otherwise, each remove operation will should decrement this by 1 till it reaches <= 1 + // and causes the edge to completely be erased from the container. + m_Dependencies.modify(it, [](Edge& e) { e.count--; }); + } } +/** + * Returns all the parent objects of the given child object. + * + * @param child The child object. + * + * @returns A list of the parent objects. + */ +std::vector DependencyGraph::GetParents(const ConfigObject::Ptr& child) +{ + std::vector objects; + + std::unique_lock lock(m_Mutex); + auto [begin, end] = m_Dependencies.get<2>().equal_range(child.get()); + std::transform(begin, end, std::back_inserter(objects), [](const Edge& edge) { + return edge.parent; + }); + + return objects; +} + +/** + * Returns all the dependent objects of the given parent object. + * + * @param parent The parent object. + * + * @returns A list of the dependent objects. + */ std::vector DependencyGraph::GetChildren(const ConfigObject::Ptr& parent) { std::vector objects; - std::unique_lock lock(m_Mutex); - auto it = m_Dependencies.find(parent.get()); - - if (it != m_Dependencies.end()) { - for (auto& kv : it->second) { - objects.emplace_back(kv.first); - } - } + std::unique_lock lock(m_Mutex); + auto [begin, end] = m_Dependencies.get<1>().equal_range(parent.get()); + std::transform(begin, end, std::back_inserter(objects), [](const Edge& edge) { + return edge.child; + }); return objects; } diff --git a/lib/base/dependencygraph.hpp b/lib/base/dependencygraph.hpp index 6afb08fc5..ef1a2a4a6 100644 --- a/lib/base/dependencygraph.hpp +++ b/lib/base/dependencygraph.hpp @@ -5,7 +5,9 @@ #include "base/i2-base.hpp" #include "base/configobject.hpp" -#include +#include +#include +#include #include namespace icinga { @@ -20,13 +22,82 @@ class DependencyGraph public: static void AddDependency(ConfigObject* child, ConfigObject* parent); static void RemoveDependency(ConfigObject* child, ConfigObject* parent); + static std::vector GetParents(const ConfigObject::Ptr& child); static std::vector GetChildren(const ConfigObject::Ptr& parent); private: DependencyGraph(); + /** + * Represents an undirected dependency edge between two objects. + * + * It allows to traverse the graph in both directions, i.e. from parent to child and vice versa. + */ + struct Edge + { + ConfigObject* parent; // The parent object of the child one. + ConfigObject* child; // The dependent object of the parent. + // Counter for the number of parent <-> child edges to allow duplicates. + int count; + + Edge(ConfigObject* parent, ConfigObject* child, int count = 1): parent(parent), child(child), count(count) + { + } + + struct Hash + { + /** + * Generates a unique hash of the given Edge object. + * + * Note, the hash value is generated only by combining the hash values of the parent and child pointers. + * + * @param edge The Edge object to be hashed. + * + * @return size_t The resulting hash value of the given object. + */ + size_t operator()(const Edge& edge) const + { + size_t seed = 0; + boost::hash_combine(seed, edge.parent); + boost::hash_combine(seed, edge.child); + + return seed; + } + }; + + struct Equal + { + /** + * Compares whether the two Edge objects contain the same parent and child pointers. + * + * Note, the member property count is not taken into account for equality checks. + * + * @param a The first Edge object to compare. + * @param b The second Edge object to compare. + * + * @return bool Returns true if the two objects are equal, false otherwise. + */ + bool operator()(const Edge& a, const Edge& b) const + { + return a.parent == b.parent && a.child == b.child; + } + }; + }; + + using DependencyMap = boost::multi_index_container< + Edge, // The value type we want to sore in the container. + boost::multi_index::indexed_by< + // The first indexer is used for lookups by the Edge from child to parent, thus it + // needs its own hash function and comparison predicate. + boost::multi_index::hashed_unique, Edge::Hash, Edge::Equal>, + // These two indexers are used for lookups by the parent and child pointers. + boost::multi_index::hashed_non_unique>, + boost::multi_index::hashed_non_unique> + > + >; + static std::mutex m_Mutex; - static std::map> m_Dependencies; + static DependencyMap m_Dependencies; }; }