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

Allow multiple callbacks on same component #6334

Merged
merged 3 commits into from
Oct 23, 2024

Conversation

knelli2
Copy link
Contributor

@knelli2 knelli2 commented Oct 15, 2024

Proposed changes

It is possible for multiple unique callbacks to be registered to the same parallel component (size control system does this). Previously, we only took the first callback and discarded the rest (because the elements would register multiple identical callbacks and we only needed one). Now we still discard identical callbacks, but keep unique callbacks on the same component.

This was found while debugging the nodegroup implementation of our algorithm, but I believe this is also a bug on develop. I guess we just have been lucky and haven't hit this so far.

Upgrade instructions

Code review checklist

  • The code is documented and the documentation renders correctly. Run
    make doc to generate the documentation locally into BUILD_DIR/docs/html.
    Then open index.html.
  • The code follows the stylistic and code quality guidelines listed in the
    code review guide.
  • The PR lists upgrade instructions and is labeled bugfix or
    new feature if appropriate.

Further comments

@knelli2 knelli2 added the bugfix label Oct 15, 2024
@knelli2 knelli2 requested review from wthrowe and nilsdeppe October 15, 2024 23:59
* \brief Returns if this callback is equal to the one passed in.
*
* \details If the `std::optional` doesn't have a value, then the pointer
* cast between the two callbacks failed. If it does have a value, then the
Copy link
Member

Choose a reason for hiding this comment

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

There's some sort of context missing for this documentation. There doesn't seem to be any mention of pointers or casts anywhere else.

std::optional<bool> is_equal_to(const Callback& rhs) const override {
const auto* downcast_ptr = dynamic_cast<const SimpleActionCallback*>(&rhs);
if (downcast_ptr == nullptr) {
return std::nullopt;
Copy link
Member

Choose a reason for hiding this comment

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

Why this distinction instead of just returning false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was particularly useful in debugging to know if the callbacks were different types, or if they were the same but had different arguments. At some point, I would like to put debugging info/prints into the cache and this kind of info would be useful, but that's beyond this PR so I just left the return types as optional<bool> for now

Copy link
Member

Choose a reason for hiding this comment

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

Just return a bool. If you want to know if the types are the same for debugging you can check that directly without complicating the public interface.

const std::optional<bool> callback_2_equal_to_vec_end =
callback_2.is_equal_to(*callbacks.back());
SPECTRE_PARALLEL_REQUIRE(callback_2_equal_to_vec_end.has_value());
SPECTRE_PARALLEL_REQUIRE_FALSE(callback_2_equal_to_vec_end.value());
Copy link
Member

Choose a reason for hiding this comment

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

[optional] callback_2_equal_to_vec_end == std::optional{false} seems clearer to me than these two lines.


namespace Parallel {
namespace detail {
// Not all tuple arguments are guaranteed to have operator==, so we check the
// ones we can.
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit worried about silently ignoring entries. What types typically occur that cause problems?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problematic types were proxies and I didn't want to deal with how to evaluate equivalence of those

Copy link
Member

Choose a reason for hiding this comment

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

OK. charmplusplus/charm#3848 is probably the real fix, for what it's worth, but we can't wait for that.

@wthrowe
Copy link
Member

wthrowe commented Oct 21, 2024

Looks good. Squash.

@knelli2
Copy link
Contributor Author

knelli2 commented Oct 21, 2024

@wthrowe I realized I inadvertently added a bug related to removing an unnecessary callback if the cache item was mutated and is now ready. I added a new commit and a new fixup, so let me know if you need more of a explanation, or if the code is enough.

Copy link
Member

@wthrowe wthrowe left a comment

Choose a reason for hiding this comment

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

Good catch. Test isn't compiling, though.

// array component id still has callbacks before trying to remove them.
if (callbacks.contains(array_component_id)) {
if (callbacks.at(array_component_id).empty()) {
callbacks.erase(array_component_id);
Copy link
Member

Choose a reason for hiding this comment

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

Check if the vector is empty after removing the element, instead of before.

@@ -115,6 +119,11 @@ class SimpleActionCallback : public Callback {
"," + pretty_type::name<Proxy>() + ")";
}

std::unique_ptr<Callback> get_clone() override {
return std::make_unique<SimpleActionCallback<SimpleAction, Proxy, Args...>>(
proxy_, args_);
Copy link
Member

Choose a reason for hiding this comment

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

[optional] Using the copy constructor is simpler. (Also several other places.)

Copy link
Member

@nilsdeppe nilsdeppe left a comment

Choose a reason for hiding this comment

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

Just a couple small things you can squash with everything else.

callbacks.erase(array_component_id);
} else {
// If this callback was a duplicate, we'll have to search through all
// callbacks to determine which to remove. If it wasn't a dupliate,
Copy link
Member

Choose a reason for hiding this comment

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

dupliate -> duplicate

Comment on lines 568 to 575
auto& vec_callbacks = callbacks.at(array_component_id);
auto it_to_erase = vec_callbacks.begin();
for (; it_to_erase != vec_callbacks.end(); it_to_erase++) {
if ((*it_to_erase)->is_equal_to(*clone_of_optional_callback)) {
vec_callbacks.erase(it_to_erase);
break;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

I think this can be done with

vec_callbacks.erase(std::find(vec_callbacks.begin(), vec_callbacks.end(),
[&clone_of_optional_callback](const auto& t) {
  return t.is_equal_to(*clone_of_optional_callback);
});

@wthrowe
Copy link
Member

wthrowe commented Oct 22, 2024

Looks good. Squash.

@knelli2 knelli2 force-pushed the cache_callback_fix branch from a77a380 to 0f096a3 Compare October 23, 2024 00:31
@nilsdeppe nilsdeppe merged commit 4e8ff38 into sxs-collaboration:develop Oct 23, 2024
21 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants