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).
This commit is contained in:
Julian Brost 2025-02-27 17:23:09 +01:00
parent 5c651e45a3
commit 4b18f62a11
3 changed files with 63 additions and 3 deletions

View File

@ -9,11 +9,13 @@
#include "base/dictionary.hpp"
#include <shared_mutex>
#include <unordered_map>
#include <boost/signals2.hpp>
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<void (const ConfigItems&)> BeforeOnAllConfigLoaded;
private:
typedef std::unordered_map<String, intrusive_ptr<ConfigObject> > ObjectMap;
typedef std::vector<intrusive_ptr<ConfigObject> > ObjectVector;

View File

@ -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<ConfigType*>(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, [&notified_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));

View File

@ -101,6 +101,39 @@ private:
static bool CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector<ConfigItem::Ptr>& 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<std::pair<ConfigItem::Ptr, bool>>& items) : m_Items(items) {}
std::vector<std::pair<ConfigItem::Ptr, bool>>& m_Items;
friend ConfigItem;
public:
/**
* ForEachObject<T>(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<typename T, typename F>
void ForEachObject(F func) const
{
for (const auto& item : m_Items) {
if (typename T::Ptr obj = dynamic_pointer_cast<T>(item.first->GetObject()); obj) {
func(std::move(obj));
}
}
}
};
}
#endif /* CONFIGITEM_H */