Skip to content

[libc++] Fix wait_on_destruct.pass.cpp hanging sometimes #146240

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

Merged
merged 4 commits into from
Jun 30, 2025

Conversation

EricWF
Copy link
Member

@EricWF EricWF commented Jun 28, 2025

This test was deadlocking on my machine.

It seems to me the intention of in_async.wait(...) was to wait for the value to be set to true, which requires a call of wait(false) (waits if value matches argument).

As a drive by change scoped_lock to unique_lock, since there shouldn't be any functional difference between the two in this test.

I've addressed the issues with the in_async by switching to a condition variable instead, since my first attempt at fixing this with in_async wasn't sufficient.

It seems to me the intention of `in_async.wait(...)` was to wait
for the value to be set to true, which requires a call of `wait(false)`
(waits if value matches argument).

As a drive by change scoped_lock to unique_lock, since there
shouldn't be any functional difference between the two in this test.

Alternatively, the test could be written with a condition variable
directly.
@EricWF EricWF requested a review from a team as a code owner June 28, 2025 21:00
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jun 28, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 28, 2025

@llvm/pr-subscribers-libcxx

Author: Eric (EricWF)

Changes

It seems to me the intention of in_async.wait(...) was to wait for the value to be set to true, which requires a call of wait(false) (waits if value matches argument).

As a drive by change scoped_lock to unique_lock, since there shouldn't be any functional difference between the two in this test.

Alternatively, the test could be written with a condition variable directly.


Full diff: https://github.com/llvm/llvm-project/pull/146240.diff

1 Files Affected:

  • (modified) libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp (+2-2)
diff --git a/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp b/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp
index 8260ec3dfcaf4..6ab9f1d0b1afb 100644
--- a/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp
+++ b/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp
@@ -30,11 +30,11 @@ int main(int, char**) {
   auto v                     = std::async(std::launch::async, [&in_async, value = 1]() mutable {
     in_async = true;
     in_async.notify_all();
-    std::scoped_lock thread_lock(mux);
+    std::unique_lock thread_lock(mux);
     value = 4;
     (void)value;
   });
-  in_async.wait(true);
+  in_async.wait(false);
   lock.unlock();
 
   return 0;

@EricWF
Copy link
Member Author

EricWF commented Jun 28, 2025

On second thought, this is actually inadequate . Since the in_async variable can go out of scope between being set to true and having notify_all set on it.

So the condition variable approach is probably best.

@philnik777
Copy link
Contributor

This test was deadlocking on my machine.

It seems to me the intention of in_async.wait(...) was to wait for the value to be set to true, which requires a call of wait(false) (waits if value matches argument).

Yes, I've got that backwards.

As a drive by change scoped_lock to unique_lock, since there shouldn't be any functional difference between the two in this test.

I've addressed the issues with the in_async by switching to a condition variable instead, since my first attempt at fixing this with in_async wasn't sufficient.

Could you elaborate? I just testted with changing in_async.wait(true) to in_async.wait(false) and it ran successfully 1000 times.

@philnik777 philnik777 changed the title Fix async test [libc++] Fix wait_on_destruct.pass.cpp hanging sometimes Jun 30, 2025
@EricWF
Copy link
Member Author

EricWF commented Jun 30, 2025

This test was deadlocking on my machine.
It seems to me the intention of in_async.wait(...) was to wait for the value to be set to true, which requires a call of wait(false) (waits if value matches argument).

Yes, I've got that backwards.

As a drive by change scoped_lock to unique_lock, since there shouldn't be any functional difference between the two in this test.
I've addressed the issues with the in_async by switching to a condition variable instead, since my first attempt at fixing this with in_async wasn't sufficient.

Could you elaborate? I just testted with changing in_async.wait(true) to in_async.wait(false) and it ran successfully 1000 times.

Yeah. So there's a race on in_async. If the async thread sets the value to true, then immediately sleeps, the main thread can run until completion, when the async thread wakes up it tries to call notify_all the stack containing in_async is gone.

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

This test was deadlocking on my machine.
It seems to me the intention of in_async.wait(...) was to wait for the value to be set to true, which requires a call of wait(false) (waits if value matches argument).

Yes, I've got that backwards.

As a drive by change scoped_lock to unique_lock, since there shouldn't be any functional difference between the two in this test.
I've addressed the issues with the in_async by switching to a condition variable instead, since my first attempt at fixing this with in_async wasn't sufficient.

Could you elaborate? I just testted with changing in_async.wait(true) to in_async.wait(false) and it ran successfully 1000 times.

Yeah. So there's a race on in_async. If the async thread sets the value to true, then immediately sleeps, the main thread can run until completion, when the async thread wakes up it tries to call notify_all the stack containing in_async is gone.

Ah, good catch!

LGTM with sleep_for removed.

Remove the sleep as requested.
@EricWF EricWF merged commit 4aaab69 into llvm:main Jun 30, 2025
10 of 13 checks passed
@EricWF
Copy link
Member Author

EricWF commented Jun 30, 2025

Thanks for the review.

Copy link

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions cpp -- libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp
View the diff from clang-format here.
diff --git a/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp b/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp
index d2964e022..7384a195b 100644
--- a/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp
+++ b/libcxx/test/std/thread/futures/futures.async/wait_on_destruct.pass.cpp
@@ -23,7 +23,6 @@
 #include <condition_variable>
 #include <thread>
 
-
 std::mutex mux;
 
 int main(int, char**) {

@EricWF
Copy link
Member Author

EricWF commented Jun 30, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
View the diff from clang-format here.

Woops. I've addressed the formatting mistake in a follow up commit.

rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
This test was deadlocking on my machine. 

It seems to me the intention of `in_async.wait(...)` was to wait for the
value to be set to true, which requires a call of `wait(false)` (waits
if value matches argument).

~As a drive by change scoped_lock to unique_lock, since there shouldn't
be any functional difference between the two in this test.~

I've addressed the issues with the `in_async` by switching to a
condition variable instead, since my first attempt at fixing this with
`in_async` wasn't sufficient.
rlavaee pushed a commit to rlavaee/llvm-project that referenced this pull request Jul 1, 2025
This test was deadlocking on my machine. 

It seems to me the intention of `in_async.wait(...)` was to wait for the
value to be set to true, which requires a call of `wait(false)` (waits
if value matches argument).

~As a drive by change scoped_lock to unique_lock, since there shouldn't
be any functional difference between the two in this test.~

I've addressed the issues with the `in_async` by switching to a
condition variable instead, since my first attempt at fixing this with
`in_async` wasn't sufficient.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants