DependencyGroup: don't change the keys of m_Members after construction

This prevents the use of DependencyGroup for storing the dependencies during
the early registration (m_DependencyGroupsPushedToRegistry = false),
m_PendingDependencies is introduced as a replacement to store the dependencies
at that time.
This commit is contained in:
Julian Brost 2025-03-13 13:13:06 +01:00 committed by Yonas Habteab
parent 945a79e37f
commit 693d094ebc
5 changed files with 46 additions and 43 deletions

View File

@ -26,15 +26,12 @@ static constexpr int l_MaxDependencyRecursionLevel(256);
void Checkable::PushDependencyGroupsToRegistry() void Checkable::PushDependencyGroupsToRegistry()
{ {
std::lock_guard lock(m_DependencyMutex); std::lock_guard lock(m_DependencyMutex);
if (!m_DependencyGroupsPushedToRegistry) { if (m_PendingDependencies != nullptr) {
m_DependencyGroupsPushedToRegistry = true; for (const auto& [key, dependencies] : *m_PendingDependencies) {
String redundancyGroup = std::holds_alternative<String>(key) ? std::get<String>(key) : "";
decltype(m_DependencyGroups) dependencyGroups; m_DependencyGroups.emplace(key, DependencyGroup::Register(new DependencyGroup(redundancyGroup, dependencies)));
m_DependencyGroups.swap(dependencyGroups);
for (auto& [dependencyGroupKey, dependencyGroup] : dependencyGroups) {
m_DependencyGroups.emplace(dependencyGroupKey, DependencyGroup::Register(dependencyGroup));
} }
m_PendingDependencies.reset();
} }
} }
@ -78,12 +75,8 @@ void Checkable::AddDependency(const Dependency::Ptr& dependency)
std::unique_lock lock(m_DependencyMutex); std::unique_lock lock(m_DependencyMutex);
auto dependencyGroupKey(GetDependencyGroupKey(dependency)); auto dependencyGroupKey(GetDependencyGroupKey(dependency));
if (!m_DependencyGroupsPushedToRegistry) { if (m_PendingDependencies != nullptr) {
auto& dependencyGroup = m_DependencyGroups[dependencyGroupKey]; (*m_PendingDependencies)[dependencyGroupKey].emplace(dependency);
if (!dependencyGroup) {
dependencyGroup = new DependencyGroup(dependency->GetRedundancyGroup());
}
dependencyGroup->AddDependency(dependency);
return; return;
} }
@ -151,10 +144,17 @@ void Checkable::RemoveDependency(const Dependency::Ptr& dependency, bool runtime
} }
} }
std::vector<Dependency::Ptr> Checkable::GetDependencies() const std::vector<Dependency::Ptr> Checkable::GetDependencies(bool includePending) const
{ {
std::unique_lock<std::mutex> lock(m_DependencyMutex); std::unique_lock<std::mutex> lock(m_DependencyMutex);
std::vector<Dependency::Ptr> dependencies; std::vector<Dependency::Ptr> dependencies;
if (includePending && m_PendingDependencies != nullptr) {
for (const auto& [group, groupDeps] : *m_PendingDependencies) {
dependencies.insert(dependencies.end(), groupDeps.begin(), groupDeps.end());
}
}
for (const auto& [_, dependencyGroup] : m_DependencyGroups) { for (const auto& [_, dependencyGroup] : m_DependencyGroups) {
auto tmpDependencies(dependencyGroup->GetDependenciesForChild(this)); auto tmpDependencies(dependencyGroup->GetDependenciesForChild(this));
dependencies.insert(dependencies.end(), tmpDependencies.begin(), tmpDependencies.end()); dependencies.insert(dependencies.end(), tmpDependencies.begin(), tmpDependencies.end());

View File

@ -190,7 +190,7 @@ public:
std::vector<intrusive_ptr<DependencyGroup>> GetDependencyGroups() const; std::vector<intrusive_ptr<DependencyGroup>> GetDependencyGroups() const;
void AddDependency(const intrusive_ptr<Dependency>& dependency); void AddDependency(const intrusive_ptr<Dependency>& dependency);
void RemoveDependency(const intrusive_ptr<Dependency>& dependency, bool runtimeRemoved = false); void RemoveDependency(const intrusive_ptr<Dependency>& dependency, bool runtimeRemoved = false);
std::vector<intrusive_ptr<Dependency> > GetDependencies() const; std::vector<intrusive_ptr<Dependency> > GetDependencies(bool includePending = false) const;
bool HasAnyDependencies() const; bool HasAnyDependencies() const;
void AddReverseDependency(const intrusive_ptr<Dependency>& dep); void AddReverseDependency(const intrusive_ptr<Dependency>& dep);
@ -251,9 +251,19 @@ private:
/* Dependencies */ /* Dependencies */
mutable std::mutex m_DependencyMutex; mutable std::mutex m_DependencyMutex;
bool m_DependencyGroupsPushedToRegistry{false};
std::map<std::variant<Checkable*, String>, intrusive_ptr<DependencyGroup>> m_DependencyGroups; std::map<std::variant<Checkable*, String>, intrusive_ptr<DependencyGroup>> m_DependencyGroups;
std::set<intrusive_ptr<Dependency> > m_ReverseDependencies; std::set<intrusive_ptr<Dependency> > m_ReverseDependencies;
/**
* Registering a checkable to its parent DependencyGroups is delayed during config loading until all dependencies
* were registered on the checkable. m_PendingDependencies is used to temporarily store the dependencies until then.
* It is a pointer type for two reasons:
* 1. The field is no longer needed after the DependencyGroups were registered, having it as a pointer reduces the
* overhead from sizeof(std::map<>) to sizeof(std::map<>*).
* 2. It allows the field to also be used as a flag: the delayed group registration is only done until it is reset
* to nullptr.
*/
std::unique_ptr<std::map<std::variant<Checkable*, String>, std::set<intrusive_ptr<Dependency>>>>
m_PendingDependencies {std::make_unique<decltype(m_PendingDependencies)::element_type>()};
void GetAllChildrenInternal(std::set<Checkable::Ptr>& seenChildren, int level = 0) const; void GetAllChildrenInternal(std::set<Checkable::Ptr>& seenChildren, int level = 0) const;

View File

@ -46,17 +46,18 @@ std::pair<std::set<Dependency::Ptr>, bool> DependencyGroup::Unregister(const Dep
{ {
std::lock_guard lock(m_RegistryMutex); std::lock_guard lock(m_RegistryMutex);
if (auto it(m_Registry.find(dependencyGroup)); it != m_Registry.end()) { if (auto it(m_Registry.find(dependencyGroup)); it != m_Registry.end()) {
auto existingGroup(*it); auto& existingGroup(*it);
auto dependencies(existingGroup->GetDependenciesForChild(child.get())); auto dependencies(existingGroup->GetDependenciesForChild(child.get()));
for (const auto& dependency : dependencies) { for (const auto& dependency : dependencies) {
existingGroup->RemoveDependency(dependency); existingGroup->RemoveDependency(dependency);
} }
if (existingGroup->IsEmpty()) { bool remove = !existingGroup->HasChildren();
if (remove) {
m_Registry.erase(it); m_Registry.erase(it);
} }
return {{dependencies.begin(), dependencies.end()}, existingGroup->IsEmpty()}; return {{dependencies.begin(), dependencies.end()}, remove};
} }
return {{}, false}; return {{}, false};
} }
@ -72,14 +73,11 @@ size_t DependencyGroup::GetRegistrySize()
return m_Registry.size(); return m_Registry.size();
} }
DependencyGroup::DependencyGroup(String name): m_RedundancyGroupName(std::move(name)) DependencyGroup::DependencyGroup(String name, const std::set<Dependency::Ptr>& dependencies)
{ : m_RedundancyGroupName(std::move(name))
}
DependencyGroup::DependencyGroup(String name, const std::set<Dependency::Ptr>& dependencies): m_RedundancyGroupName(std::move(name))
{ {
for (const auto& dependency : dependencies) { for (const auto& dependency : dependencies) {
AddDependency(dependency); m_Members[MakeCompositeKeyFor(dependency)].emplace(dependency->GetChild().get(), dependency.get());
} }
} }
@ -103,14 +101,14 @@ DependencyGroup::CompositeKeyType DependencyGroup::MakeCompositeKeyFor(const Dep
} }
/** /**
* Check if the current dependency group is empty. * Check if the current dependency has any children.
* *
* @return bool - Returns true if the current dependency group has no members, otherwise false. * @return bool - Returns true if the current dependency group has children, otherwise false.
*/ */
bool DependencyGroup::IsEmpty() const bool DependencyGroup::HasChildren() const
{ {
std::lock_guard lock(m_Mutex); std::lock_guard lock(m_Mutex);
return m_Members.empty(); return std::any_of(m_Members.begin(), m_Members.end(), [](const auto& pair) { return !pair.second.empty(); });
} }
/** /**
@ -142,7 +140,6 @@ void DependencyGroup::LoadParents(std::set<Checkable::Ptr>& parents) const
{ {
std::lock_guard lock(m_Mutex); std::lock_guard lock(m_Mutex);
for (auto& [compositeKey, children] : m_Members) { for (auto& [compositeKey, children] : m_Members) {
ASSERT(!children.empty()); // We should never have an empty map for any given key at any given time.
parents.insert(std::get<0>(compositeKey)); parents.insert(std::get<0>(compositeKey));
} }
} }
@ -174,11 +171,12 @@ void DependencyGroup::AddDependency(const Dependency::Ptr& dependency)
{ {
std::lock_guard lock(m_Mutex); std::lock_guard lock(m_Mutex);
auto compositeKey(MakeCompositeKeyFor(dependency)); auto compositeKey(MakeCompositeKeyFor(dependency));
if (auto it(m_Members.find(compositeKey)); it != m_Members.end()) { auto it = m_Members.find(compositeKey);
// The dependency must be compatible with the group, i.e. its parent config must be known in the group already.
VERIFY(it != m_Members.end());
it->second.emplace(dependency->GetChild().get(), dependency.get()); it->second.emplace(dependency->GetChild().get(), dependency.get());
} else {
m_Members.emplace(compositeKey, MemberValueType{{dependency->GetChild().get(), dependency.get()}});
}
} }
/** /**
@ -196,10 +194,6 @@ void DependencyGroup::RemoveDependency(const Dependency::Ptr& dependency)
// This will also remove the child Checkable from the multimap container // This will also remove the child Checkable from the multimap container
// entirely if this was the last child of it. // entirely if this was the last child of it.
it->second.erase(childrenIt); it->second.erase(childrenIt);
// If the composite key has no more children left, we can remove it entirely as well.
if (it->second.empty()) {
m_Members.erase(it);
}
return; return;
} }
} }

View File

@ -123,7 +123,7 @@ public:
} }
// Explicitly configured dependency objects // Explicitly configured dependency objects
for (const auto& dep : checkable->GetDependencies()) { for (const auto& dep : checkable->GetDependencies(/* includePending = */ true)) {
m_Stack.emplace_back(dep); m_Stack.emplace_back(dep);
AssertNoCycle(dep->GetParent()); AssertNoCycle(dep->GetParent());
m_Stack.pop_back(); m_Stack.pop_back();

View File

@ -132,7 +132,6 @@ public:
using MemberValueType = std::unordered_multimap<const Checkable*, Dependency*>; using MemberValueType = std::unordered_multimap<const Checkable*, Dependency*>;
using MembersMap = std::map<CompositeKeyType, MemberValueType>; using MembersMap = std::map<CompositeKeyType, MemberValueType>;
explicit DependencyGroup(String name);
DependencyGroup(String name, const std::set<Dependency::Ptr>& dependencies); DependencyGroup(String name, const std::set<Dependency::Ptr>& dependencies);
static DependencyGroup::Ptr Register(const DependencyGroup::Ptr& dependencyGroup); static DependencyGroup::Ptr Register(const DependencyGroup::Ptr& dependencyGroup);
@ -151,7 +150,7 @@ public:
return !m_RedundancyGroupName.IsEmpty(); return !m_RedundancyGroupName.IsEmpty();
} }
bool IsEmpty() const; bool HasChildren() const;
void AddDependency(const Dependency::Ptr& dependency); void AddDependency(const Dependency::Ptr& dependency);
void RemoveDependency(const Dependency::Ptr& dependency); void RemoveDependency(const Dependency::Ptr& dependency);
std::vector<Dependency::Ptr> GetDependenciesForChild(const Checkable* child) const; std::vector<Dependency::Ptr> GetDependenciesForChild(const Checkable* child) const;