ConfigItem::CommitNewItems(): pre-sort types by their load dependencies once#10003
ConfigItem::CommitNewItems(): pre-sort types by their load dependencies once#10003
Conversation
9726620 to
3ffa26a
Compare
3ffa26a to
19aa779
Compare
19aa779 to
e1f5ab8
Compare
lib/base/type.hpp
Outdated
| static std::vector<Type::Ptr> GetAllTypes(); | ||
| static const std::vector<Ptr>& GetAllTypesSortedByLoadDependencies(); |
There was a problem hiding this comment.
Those two names suggest that the vectors should contain the same elements, just in a different order. However, there's some additional filtering:
Lines 49 to 51 in e1f5ab8
This should be reflected in the name.
lib/base/type.cpp
Outdated
| static std::vector<Type::Ptr> l_SortedByLoadDependencies; | ||
| static Atomic l_SortingByLoadDependenciesDone (false); | ||
|
|
||
| typedef std::unordered_map<Type*, bool> Visited; // https://stackoverflow.com/a/8942986 |
There was a problem hiding this comment.
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
-
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(). ↩
lib/base/type.cpp
Outdated
| for (auto& type : types) { | ||
| visited[type.get()] = false; | ||
| } |
There was a problem hiding this comment.
This could also be just visited.clear(), but I why is this done at all, you shouldn't have to revisit anything.
lib/base/type.cpp
Outdated
| } | ||
|
|
||
| bool& alreadyVisited (visited.at(type)); | ||
| VERIFY(!alreadyVisited); |
There was a problem hiding this comment.
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.
lib/base/type.cpp
Outdated
| } | ||
|
|
||
| VERIFY(sorted.size() == types.size()); | ||
| VERIFY(sorted[0]->GetLoadDependencies().empty()); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
What about a test case
I was thinking of a unit test when writing that.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
The sorted var and the "atomic" std::swap() at the end (which I consider good practice) has no disadvantages IMAO.
| if (unsorted.find(type) == unsorted.end()) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
... 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().
| types.erase(std::remove_if(types.begin(), types.end(), [](auto& type) { | ||
| return !ConfigObject::TypeInstance->IsAssignableFrom(type); | ||
| }), types.end()); |
There was a problem hiding this comment.
I would also inline this check into the visit callback, since you don't have to remove those unwanted types beforehand.
There was a problem hiding this comment.
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.
e1f5ab8 to
a422419
Compare
As just discussed offline, this is being superseded by #10148, so please review that PR instead.
to avoid complicated nested loops, iterating over the same types and
checking dependencies over and over, skipping already completed ones.
closes #10148