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

Warn in ScopeGuard about illegal usage #5250

Merged
merged 4 commits into from
Jul 26, 2022
Merged

Conversation

crtrott
Copy link
Member

@crtrott crtrott commented Jul 21, 2022

We (Damien and I) are considering a change of behavior and how to use it for ScopeGuard.
Specifically, we believe its current design is problematic in various ways, one of them being the silent ignoring of arguments handed to it if Kokkos was already initialized.

Besides better warnings in cases which were previously already broken (like creating a ScopeGuard after Kokkos::finalize() was called), the primary change is that you can't create a ScopeGuard after Kokkos::initialize was called. This also means that you can't create a ScopeGuard after any other ScopeGuard was created. The error message provided shows a way how to do nested scopeguards (as they might currently be in use) if really desired, but essentially its pushing more of the correctness check behavior on the user.

Please let us know what you think.

Note: this PR is missing tests, which I am working on.

core/src/Kokkos_Core.hpp Outdated Show resolved Hide resolved
core/src/Kokkos_Core.hpp Show resolved Hide resolved
core/src/Kokkos_Core.hpp Show resolved Hide resolved
core/src/Kokkos_Core.hpp Show resolved Hide resolved
core/src/Kokkos_Core.hpp Show resolved Hide resolved
core/src/Kokkos_Core.hpp Show resolved Hide resolved
class KOKKOS_ATTRIBUTE_NODISCARD ScopeGuard {
public:
#if defined(__has_cpp_attribute) && __has_cpp_attribute(nodiscard) >= 201907
KOKKOS_ATTRIBUTE_NODISCARD
#endif
ScopeGuard(int& argc, char* argv[]) {
sg_init = false;
#ifdef KOKKOS_ENABLE_DEPRECATION_WARNINGS
if (is_initialized()) {
Copy link
Member

@dalg24 dalg24 Jul 21, 2022

Choose a reason for hiding this comment

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

Suggested change
if (is_initialized()) {
if (is_initialized() || is_finalized()) {

I hope you will appreciate the irony here. Technically the code you propose here has a bug that the change I suggest above fixes. With the definition proposed in #5245 that would have be right.

If you are trying to only emit one of the warning your code should read

    if (is_finalized()) {
       std::cerr << Impl::scopeguard_create_after_finalize_warning()
                 << std::endl;
     } else if (is_initialized() || is_finalized()) {  // obviously the || is_finallized() can be discarded
                                                        // but I can't believe you can write that code and not see
                                                        // I was right and that the option 2 in #5208 is more logical
      std::cerr << Impl::scopeguard_create_after_initialize_warning()
                 << std::endl;
     }

core/src/Kokkos_Core.hpp Show resolved Hide resolved
core/src/Kokkos_Core.hpp Outdated Show resolved Hide resolved
Copy link
Member

@dalg24 dalg24 left a comment

Choose a reason for hiding this comment

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

I like the use of function to produce the strings and the general organization besides the comments I made

Comment on lines 312 to 320
ScopeGuard(int& argc, char* argv[]) {
if (is_initialized()) {
Kokkos::abort(Impl::scopeguard_create_after_init_warning().c_str());
}
if (is_finalized()) {
Kokkos::abort(Impl::scopeguard_create_after_finalize_warning().c_str());
}
initialize(argc, argv);
}
Copy link
Member

Choose a reason for hiding this comment

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

Commenting so I get a souvenir after Christian renames everything

Copy link
Member

@dalg24 dalg24 left a comment

Choose a reason for hiding this comment

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

Looks good now.
Did you plan on adding tests?

Copy link
Contributor

@masterleinad masterleinad left a comment

Choose a reason for hiding this comment

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

Looks good to me but you still want to add tests, right?

Comment on lines 300 to 304
#if defined(__has_cpp_attribute) && __has_cpp_attribute(nodiscard) >= 201907
KOKKOS_ATTRIBUTE_NODISCARD
#endif

ScopeGuard(int& argc, char* argv[]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#if defined(__has_cpp_attribute) && __has_cpp_attribute(nodiscard) >= 201907
KOKKOS_ATTRIBUTE_NODISCARD
#endif
ScopeGuard(int& argc, char* argv[]) {
#if defined(__has_cpp_attribute) && __has_cpp_attribute(nodiscard) >= 201907
KOKKOS_ATTRIBUTE_NODISCARD
#endif
ScopeGuard(int& argc, char* argv[]) {

finalize();
}

// private:
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// private:

@@ -287,6 +393,7 @@ std::vector<ExecSpace> partition_space(ExecSpace space,

// Specializations requires after core definitions
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Specializations requires after core definitions
// Specializations required after core definitions

?

#if defined(__has_cpp_attribute) && __has_cpp_attribute(nodiscard) >= 201907
KOKKOS_ATTRIBUTE_NODISCARD
#endif
ScopeGuard(
Copy link
Contributor

@nliber nliber Jul 22, 2022

Choose a reason for hiding this comment

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

The single argument constructor should be explicit.

explicit ScopeGuard(const InitializationSettings& settings) { /* ... */ }
ScopeGuard() : ScopeGuard(InitializationSettings()) {}

Note: I haven't quite thought through the implications if we end up keeping InitArguments around for a while as well, as that might require its own (possibly implicit) forwarding constructor for backwards compatibility.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed on the explicit comment

Interaction with InitArguments will be fine https://godbolt.org/z/7scMhjMj6

Copy link
Contributor

@nliber nliber Jul 22, 2022

Choose a reason for hiding this comment

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

Before we added InitializationSettings, the following three initialization syntaxes work:

    InitArguments ia;
    
    ScopeGuard sc0 = ia;
    ScopeGuard sc1(ia);
    ScopeGuard sc2{ia};

If we only do explicit ScopeGuard(const InitializationSettings& settings) { /* ... */ }, sc0 no longer compiles.

For the record, I'm fine with breaking that. I'm just pointing out that it is a breaking change (albeit unrelated to the explicitness of the constructor).

If we don't want it to break, we need to add:

/* implicit */ ScopeGuard(const InitArguments& ia) : ScopeGuard(InitializationSettings(ia)) {}

https://godbolt.org/z/4d6ra7rh5

Copy link
Member

Choose a reason for hiding this comment

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

I tend to think it is fine to break the ScopeGuard sc0 = ia; use.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree.

Copy link
Member

Choose a reason for hiding this comment

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

@crtrott you forgot to add explicit here. Considering that this is the new code path, I would have been fine with omitting at the other place but it must be added here.


// private:
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(const ScopeGuard&) = delete;
Copy link
Contributor

@nliber nliber Jul 22, 2022

Choose a reason for hiding this comment

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

While not strictly necessary, please add the other two special member functions (Rule of 5):

ScopeGuard& operator=(ScopeGuard&&) = delete;
ScopeGuard(ScopeGuard&&) = delete;

@crtrott
Copy link
Member Author

crtrott commented Jul 25, 2022

I won't have time to write tests for 3.7 here is a standalone manual test

#include <Kokkos_Core.hpp>
#include <cmath>

int main(int argc, char* argv[]) {
  {
    int create_after_init = argc > 1 ? atoi(argv[1]) : 0;
    int create_after_final = argc > 2 ? atoi(argv[2]) : 0;
    int create_after_other = argc > 3 ? atoi(argv[3]) : 0;
    int destroy_after_final = argc > 4 ? atoi(argv[4]) : 0;
    if(create_after_other) {
      Kokkos::ScopeGuard guard1(argc,argv);
      Kokkos::ScopeGuard guard2(argc,argv);

    } else {
      if(create_after_init || create_after_final)
        Kokkos::initialize(argc, argv);
      if(create_after_final)
        Kokkos::finalize();
      Kokkos::ScopeGuard guard(argc,argv);
      if((!create_after_final && create_after_init) || destroy_after_final)
        Kokkos::finalize();
    }
  }
}

Test results deprecated_code disabled is this, I also confirmed that I get that output with just deprecation warnings on.

[crtrott@kokkos-dev-2 ScopeGuard]$ ./test.cuda 0 0 0 0
[crtrott@kokkos-dev-2 ScopeGuard]$ ./test.cuda 1 0 0 0
Kokkos Error: Creating a ScopeGuard while Kokkos is initialized is illegal.
Do instead:
  std::unique_ptr<Kokkos::ScopeGuard> guard =
    !Kokkos::is_initialized() && !Kokkos::is_finalized()?
    new ScopeGuard(argc,argv) : nullptr;

Backtrace:
                        [0x447473]
                        [0x41b398]
                        [0x41b41b]
                        [0x407731]
                        [0x4058c6]
__libc_start_main [0x7fa3e7bcf555]
                        [0x406d79]
Aborted (core dumped)
[crtrott@kokkos-dev-2 ScopeGuard]$ ./test.cuda 0 1 0 0
Kokkos Error: Creating a ScopeGuard after Kokkos was finalized is illegal.
Do instead:
  std::unique_ptr<Kokkos::ScopeGuard> guard =
    !Kokkos::is_initialized() && !Kokkos::is_finalized()?
    new ScopeGuard(argc,argv) : nullptr;

Backtrace:
                        [0x447473]
                        [0x41b398]
                        [0x41b41b]
                        [0x407763]
                        [0x405895]
__libc_start_main [0x7fa271384555]
                        [0x406d79]
Aborted (core dumped)
[crtrott@kokkos-dev-2 ScopeGuard]$ ./test.cuda 0 0 1 0
Kokkos Error: Creating a ScopeGuard while Kokkos is initialized is illegal.
Do instead:
  std::unique_ptr<Kokkos::ScopeGuard> guard =
    !Kokkos::is_initialized() && !Kokkos::is_finalized()?
    new ScopeGuard(argc,argv) : nullptr;

Backtrace:
                        [0x447473]
                        [0x41b398]
                        [0x41b41b]
                        [0x407731]
                        [0x405965]
__libc_start_main [0x7f8351ae5555]
                        [0x406d79]
Aborted (core dumped)
[crtrott@kokkos-dev-2 ScopeGuard]$ ./test.cuda 0 0 0 1
Kokkos Error: Destroying a ScopeGuard after Kokkos was finalized is illegal.
Do instead:
  std::unique_ptr<Kokkos::ScopeGuard> guard =
    !Kokkos::is_initialized() && !Kokkos::is_finalized()?
    new ScopeGuard(argc,argv) : nullptr;

Backtrace:
                        [0x447473]
                        [0x41b398]
                        [0x41b41b]
                        [0x407798]
                        [0x4058a2]
__libc_start_main [0x7fb800433555]
                        [0x406d79]
Aborted (core dumped)

@crtrott crtrott added the Blocks Promotion Overview issue for release-blocking bugs label Jul 25, 2022
@@ -334,9 +335,11 @@ class KOKKOS_ATTRIBUTE_NODISCARD ScopeGuard {
finalize();
}

// private:
private:
Copy link
Member

Choose a reason for hiding this comment

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

I am going to pretend I don't see that guy

"Do instead:\n"
" std::unique_ptr<Kokkos::ScopeGuard> guard =\n"
" !Kokkos::is_initialized() && !Kokkos::is_finalized()?\n"
" new ScopeGuard(argc,argv) : nullptr;\n");
Copy link
Member

Choose a reason for hiding this comment

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

I can live with it but I wanted to point out that this error will be raised even when the InitializationSettings argument was used.

Comment on lines +339 to +342
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard& operator=(ScopeGuard&&) = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&&) = delete;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are these private?

Copy link
Member Author

Choose a reason for hiding this comment

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

because they were private before

Copy link
Member Author

Choose a reason for hiding this comment

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

or at least meant to be private??

Copy link
Contributor

Choose a reason for hiding this comment

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

It's more idiomatic not to have private copy constructors if you meant to delete them, right? This feels like part of a pre-C++11 approach where you couldn't use delete.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @masterleinad here.

Before C++11, the copy operations always existed for every class, so to make them noncopyable one made them private and unimplemented (or privately derived from something like boost::noncopyable, which did the same thing). Unimplemented always gave a linker error on an attempted copy, and making them private sometimes gave a compile time access error (depending on usage).

No need for that now that we can delete functions.

Comment on lines +339 to +342
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard& operator=(ScopeGuard&&) = delete;
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&&) = delete;
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree with @masterleinad here.

Before C++11, the copy operations always existed for every class, so to make them noncopyable one made them private and unimplemented (or privately derived from something like boost::noncopyable, which did the same thing). Unimplemented always gave a linker error on an attempted copy, and making them private sometimes gave a compile time access error (depending on usage).

No need for that now that we can delete functions.

@crtrott
Copy link
Member Author

crtrott commented Jul 26, 2022

I removed the private ...

Copy link
Contributor

@masterleinad masterleinad left a comment

Choose a reason for hiding this comment

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

Thanks!

@dalg24
Copy link
Member

dalg24 commented Jul 26, 2022

Failures were unrelated (machine running out of space)

@dalg24 dalg24 merged commit 8e5ae2b into kokkos:develop Jul 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Blocks Promotion Overview issue for release-blocking bugs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants