Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ConfigItem::CommitNewItems(): pre-sort types by their load dependencies once #10003

Closed
wants to merge 2 commits into from

Conversation

Al2Klimov
Copy link
Member

@Al2Klimov Al2Klimov commented Feb 15, 2024

to avoid complicated nested loops, iterating over the same types and
checking dependencies over and over, skipping already completed ones.

closes #10148

@Al2Klimov Al2Klimov added the core/quality Improve code, libraries, algorithms, inline docs label Feb 15, 2024
@cla-bot cla-bot bot added the cla/signed label Feb 15, 2024
lib/config/configitem.cpp Outdated Show resolved Hide resolved
lib/base/type.cpp Outdated Show resolved Hide resolved
@yhabteab yhabteab removed their request for review February 21, 2024 12:53
@Al2Klimov Al2Klimov self-assigned this Aug 20, 2024
@julianbrost julianbrost self-assigned this Aug 27, 2024
lib/base/type.cpp Outdated Show resolved Hide resolved
@julianbrost julianbrost removed their assignment Aug 29, 2024
Comment on lines 84 to 85
static std::vector<Type::Ptr> GetAllTypes();
static const std::vector<Ptr>& GetAllTypesSortedByLoadDependencies();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those two names suggest that the vectors should contain the same elements, just in a different order. However, there's some additional filtering:

types.erase(std::remove_if(types.begin(), types.end(), [](auto& type) {
return !ConfigObject::TypeInstance->IsAssignableFrom(type);
}), types.end());

This should be reflected in the name.

static std::vector<Type::Ptr> l_SortedByLoadDependencies;
static Atomic l_SortingByLoadDependenciesDone (false);

typedef std::unordered_map<Type*, bool> Visited; // https://stackoverflow.com/a/8942986
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO a sentence would be more helpful than a link to a "try typedef" answer, where (at least to me), it's not that obvious why this applies here.1

Anyway, the way you use this type, I don't see a reason why this couldn't be a std::unordered_set<Type*> instead, and checking if an element is contained instead of checking the bool value.

Footnotes

  1. The question is about SET_TYPE_NAME(std::map<int, int>, "TheMap") which is way simpler what's happening here. There, I'd find it totally plausible if the C preprocessor would not care about the C++ template parameter syntax and treat < and > as separate operators. The lambda used here as a template argument contains multiple , that aren't misinterpreted, so they are not a general problem as the C preprocessor understands some C syntax. The relevant part here is that even though {} are also part of the C syntax, the C preprocessor doesn't seem to care too much about them. I haven't found a definite answer, but it looks like it cares only for string/character literals and ().

Comment on lines 81 to 83
for (auto& type : types) {
visited[type.get()] = false;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be just visited.clear(), but I why is this done at all, you shouldn't have to revisit anything.

}

bool& alreadyVisited (visited.at(type));
VERIFY(!alreadyVisited);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that VERIFY() cycle detection in disguise? It would be more helpful if it actually said so as it would give you more of a clue of what happened if you manage to trigger it.

}

VERIFY(sorted.size() == types.size());
VERIFY(sorted[0]->GetLoadDependencies().empty());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a nice sanity check, but (obviously) just checks the first element. What about a test case for the whole order? Like iterate the whole vector and for each element, check that it's dependencies are placed at a lower index.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a test case for the whole order?

This is unnecessary IMHO, given that if the orders get messed up, you won't be able to start Icinga 2 anyway, so the startup phase actually takes care of such sanity checks and that function should just do what it is meant to do.

Copy link
Contributor

@julianbrost julianbrost Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the startup phase actually takes care of such sanity checks

Does it check that explicitly? Or is it just that there's a good chance you'll get some errors then (but maybe it could just be broken in a subtle way)?

Copy link
Member

@yhabteab yhabteab Sep 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not explicitly, but the type orders do matter when starting Icinga 2, and if you insist on introducing such explicit sanity checks, well then unittest it, but this function should not be bothered in checking the final result.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a test case

I was thinking of a unit test when writing that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a test case

I was thinking of a unit test when writing that.

I thought you'd want to loop through the final result at the end and check their orders!

// Depth-first search
std::unordered_set<Type*> unsorted;
Visited visited;
std::vector<Type::Ptr> sorted;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to temporarily cache the sorted ones elsewhere than l_SortedByLoadDependencies? It is guarded by VERIFY(l_SortingByLoadDependenciesDone.load()); so why can't you put the sorted ones directly in their final location.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sorted var and the "atomic" std::swap() at the end (which I consider good practice) has no disadvantages IMAO.

Comment on lines +64 to +63
if (unsorted.find(type) == unsorted.end()) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If, for whatever reason, you define in the *.ti files that a type should be loaded after a random type that is not registered in the Types namespace, then you can be sure that you will not get a Type* if you load it with Type#GetLoadDependencies(), but a nullptr instead. So I would drop unsorted entirely and perform a ...

alreadyVisited = true;

for (auto dep : type->GetLoadDependencies()) {
visit(dep);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... dep != nullptr instead. Because if you put this in the host.ti file, load_after awegqa24gtw34gtw34; it will try to find this bogus type via GetByName("awegqa24gtw34gtw34").get(), which obviously doesn't exist, and store a nullptr in the load dependencies set that is returned when you call host->GetLoadDependencies().

Comment on lines +49 to +48
types.erase(std::remove_if(types.begin(), types.end(), [](auto& type) {
return !ConfigObject::TypeInstance->IsAssignableFrom(type);
}), types.end());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also inline this check into the visit callback, since you don't have to remove those unwanted types beforehand.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I need to VERIFY(sorted.size() == types.size());

…es once

to avoid complicated nested loops, iterating over the same types and
checking dependencies over and over, skipping already completed ones.
@yhabteab yhabteab dismissed their stale review September 16, 2024 10:55

As just discussed offline, this is being superseded by #10148, so please review that PR instead.

@yhabteab yhabteab removed their request for review September 16, 2024 10:55
@icinga-probot icinga-probot bot deleted the Type-SortByLoadDependencies branch September 26, 2024 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla/signed core/quality Improve code, libraries, algorithms, inline docs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants