From 4b18f62a11cbefb6e25e2fd625ad18e9a899edf1 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 27 Feb 2025 17:23:09 +0100 Subject: [PATCH] Add ConfigType::BeforeOnAllConfigLoaded signal Allows to hook into the config loading process just before OnAllConfigLoaded() is called on a bunch of individual config objects. Allows doing some operations more efficiently at once for all objects. Intended use: when adding a number of dependencies, it has to be checked whether this uses any cycles. This can be done more efficiently if all dependencies are checked at once. So far, this is with a case-distinction for initially loaded files in DaemonUtility::LoadConfigFiles() and for dependencies created by runtime updates in Dependency::OnAllConfigLoaded(). The mechanism added by this commit allows to unify the handling of both cases (done in a following commit). --- lib/base/configtype.hpp | 9 +++++++++ lib/config/configitem.cpp | 24 +++++++++++++++++++++--- lib/config/configitem.hpp | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/lib/base/configtype.hpp b/lib/base/configtype.hpp index c77fc5ec2..f709b2c0d 100644 --- a/lib/base/configtype.hpp +++ b/lib/base/configtype.hpp @@ -9,11 +9,13 @@ #include "base/dictionary.hpp" #include #include +#include namespace icinga { class ConfigObject; +class ConfigItems; class ConfigType { @@ -48,6 +50,13 @@ for (const auto& object : objects) { int GetObjectCount() const; + /** + * Signal that allows hooking into the config loading process just before ConfigObject::OnAllConfigLoaded() is + * called for a bunch of objects. A vector of pointers to these objects is passed as an argument. All elements + * are of the object type the signal is called on. + */ + boost::signals2::signal BeforeOnAllConfigLoaded; + private: typedef std::unordered_map > ObjectMap; typedef std::vector > ObjectVector; diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index e8a509275..5dce19eda 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -499,6 +499,23 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue auto items (itemsByType.find(type.get())); if (items != itemsByType.end()) { + auto configType = dynamic_cast(type.get()); + + // Skip the call if no handlers are connected (signal::empty()) or there are no items (vector::empty()). + if (configType && !configType->BeforeOnAllConfigLoaded.empty() && !items->second.empty()) { + // Call the signal in the WorkQueue so that if an exception is thrown, it is caught by the WorkQueue + // and then reported like any other config validation error. + upq.Enqueue([&configType, &items]() { + configType->BeforeOnAllConfigLoaded(ConfigItems(items->second)); + }); + + upq.Join(); + + if (upq.HasExceptions()) { + return false; + } + } + upq.ParallelFor(items->second, [¬ified_items](const ItemPair& ip) { const ConfigItem::Ptr& item = ip.first; @@ -525,6 +542,10 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue }); upq.Join(); + + if (upq.HasExceptions()) { + return false; + } } } @@ -534,9 +555,6 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue << "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'."; #endif /* I2_DEBUG */ - if (upq.HasExceptions()) - return false; - notified_items = 0; for (auto loadDep : type->GetLoadDependencies()) { auto items (itemsByType.find(loadDep)); diff --git a/lib/config/configitem.hpp b/lib/config/configitem.hpp index b99cd08e5..007a3c08a 100644 --- a/lib/config/configitem.hpp +++ b/lib/config/configitem.hpp @@ -101,6 +101,39 @@ private: static bool CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector& newItems); }; +/** + * Helper class for exposing config items being committed to the ConfigType::BeforeOnAllConfigLoaded callback. + * + * This class wraps a reference to an internal data structure used in ConfigItem::CommitNewItems() and provides + * functions useful for the callbacks without exposing the internals of CommitNewItems(). + */ +class ConfigItems +{ + explicit ConfigItems(std::vector>& items) : m_Items(items) {} + + std::vector>& m_Items; + + friend ConfigItem; + +public: + /** + * ForEachObject(f) calls f(t) for each object T::Ptr t in vector of underlying config items. + * + * @tparam T ConfigObject type to iterate over + * @tparam F Callback functor type (usually automatically deduced from func) + * @param func Functor accepting T::Ptr as an argument to be called for each object + */ + template + void ForEachObject(F func) const + { + for (const auto& item : m_Items) { + if (typename T::Ptr obj = dynamic_pointer_cast(item.first->GetObject()); obj) { + func(std::move(obj)); + } + } + } +}; + } #endif /* CONFIGITEM_H */