Skip to content

Commit ac88ad3

Browse files
authored
[libc++] Refactor tests for std::condition_variable (#91530)
These tests have always been flaky, which led us to using ALLOW_RETRIES on them. However, while investigating #89083 (using Github provided macOS builders), these tests surfaced as being basically unworkably flaky in that environment. This patch solves that problem by refactoring the tests to make them succeed deterministically.
1 parent 44430de commit ac88ad3

File tree

10 files changed

+1088
-700
lines changed

10 files changed

+1088
-700
lines changed

libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for.pass.cpp

Lines changed: 80 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66
//
77
//===----------------------------------------------------------------------===//
8-
//
9-
// UNSUPPORTED: no-threads
10-
// ALLOW_RETRIES: 2
8+
9+
// UNSUPPORTED: no-threads, c++03
1110

1211
// <condition_variable>
1312

@@ -19,77 +18,92 @@
1918
// const chrono::duration<Rep, Period>& rel_time);
2019

2120
#include <condition_variable>
21+
#include <atomic>
22+
#include <cassert>
23+
#include <chrono>
2224
#include <mutex>
2325
#include <thread>
24-
#include <chrono>
25-
#include <cassert>
2626

2727
#include "make_test_thread.h"
2828
#include "test_macros.h"
2929

30-
std::condition_variable cv;
31-
std::mutex mut;
32-
33-
int test1 = 0;
34-
int test2 = 0;
35-
36-
bool expect_timeout = false;
37-
38-
void f()
39-
{
40-
typedef std::chrono::system_clock Clock;
41-
typedef std::chrono::milliseconds milliseconds;
42-
std::unique_lock<std::mutex> lk(mut);
43-
assert(test2 == 0);
44-
test1 = 1;
45-
cv.notify_one();
46-
Clock::time_point t0 = Clock::now();
47-
Clock::time_point wait_end = t0 + milliseconds(250);
48-
Clock::duration d;
49-
do {
50-
d = wait_end - Clock::now();
51-
if (d <= milliseconds(0)) break;
52-
} while (test2 == 0 && cv.wait_for(lk, d) == std::cv_status::no_timeout);
53-
Clock::time_point t1 = Clock::now();
54-
if (!expect_timeout)
55-
{
56-
assert(t1 - t0 < milliseconds(250));
57-
assert(test2 != 0);
58-
}
59-
else
60-
{
61-
assert(t1 - t0 - milliseconds(250) < milliseconds(50));
62-
assert(test2 == 0);
63-
}
30+
template <class Function>
31+
std::chrono::microseconds measure(Function f) {
32+
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
33+
f();
34+
std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
35+
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
6436
}
6537

66-
int main(int, char**)
67-
{
68-
{
69-
std::unique_lock<std::mutex> lk(mut);
70-
std::thread t = support::make_test_thread(f);
71-
assert(test1 == 0);
72-
while (test1 == 0)
73-
cv.wait(lk);
74-
assert(test1 != 0);
75-
test2 = 1;
76-
lk.unlock();
77-
cv.notify_one();
78-
t.join();
79-
}
80-
test1 = 0;
81-
test2 = 0;
82-
expect_timeout = true;
83-
{
84-
std::unique_lock<std::mutex> lk(mut);
85-
std::thread t = support::make_test_thread(f);
86-
assert(test1 == 0);
87-
while (test1 == 0)
88-
cv.wait(lk);
89-
assert(test1 != 0);
90-
lk.unlock();
91-
t.join();
92-
}
38+
int main(int, char**) {
39+
// Test unblocking via a call to notify_one() in another thread.
40+
//
41+
// To test this, we set a very long timeout in wait_for() and we wait
42+
// again in case we get awoken spuriously. Note that it can actually
43+
// happen that we get awoken spuriously and fail to recognize it
44+
// (making this test useless), but the likelihood should be small.
45+
{
46+
std::atomic<bool> ready(false);
47+
std::atomic<bool> likely_spurious(true);
48+
auto timeout = std::chrono::seconds(3600);
49+
std::condition_variable cv;
50+
std::mutex mutex;
51+
52+
std::thread t1 = support::make_test_thread([&] {
53+
std::unique_lock<std::mutex> lock(mutex);
54+
auto elapsed = measure([&] {
55+
ready = true;
56+
do {
57+
std::cv_status result = cv.wait_for(lock, timeout);
58+
assert(result == std::cv_status::no_timeout);
59+
} while (likely_spurious);
60+
});
61+
62+
// This can technically fail if we have many spurious awakenings, but in practice the
63+
// tolerance is so high that it shouldn't be a problem.
64+
assert(elapsed < timeout);
65+
});
66+
67+
std::thread t2 = support::make_test_thread([&] {
68+
while (!ready) {
69+
// spin
70+
}
71+
72+
// Acquire the same mutex as t1. This blocks the condition variable inside its wait call
73+
// so we can notify it while it is waiting.
74+
std::unique_lock<std::mutex> lock(mutex);
75+
cv.notify_one();
76+
likely_spurious = false;
77+
lock.unlock();
78+
});
79+
80+
t2.join();
81+
t1.join();
82+
}
83+
84+
// Test unblocking via a timeout.
85+
//
86+
// To test this, we create a thread that waits on a condition variable
87+
// with a certain timeout, and we never awaken it. To guard against
88+
// spurious wakeups, we wait again whenever we are awoken for a reason
89+
// other than a timeout.
90+
{
91+
auto timeout = std::chrono::milliseconds(250);
92+
std::condition_variable cv;
93+
std::mutex mutex;
94+
95+
std::thread t1 = support::make_test_thread([&] {
96+
std::unique_lock<std::mutex> lock(mutex);
97+
std::cv_status result;
98+
do {
99+
auto elapsed = measure([&] { result = cv.wait_for(lock, timeout); });
100+
if (result == std::cv_status::timeout)
101+
assert(elapsed >= timeout);
102+
} while (result != std::cv_status::timeout);
103+
});
104+
105+
t1.join();
106+
}
93107

94108
return 0;
95109
}

libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for_pred.pass.cpp

Lines changed: 129 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66
//
77
//===----------------------------------------------------------------------===//
8-
//
9-
// UNSUPPORTED: no-threads
10-
// ALLOW_RETRIES: 2
8+
9+
// UNSUPPORTED: no-threads, c++03
1110

1211
// <condition_variable>
1312

@@ -20,82 +19,141 @@
2019
// Predicate pred);
2120

2221
#include <condition_variable>
22+
#include <atomic>
23+
#include <cassert>
24+
#include <chrono>
2325
#include <mutex>
2426
#include <thread>
25-
#include <chrono>
26-
#include <cassert>
2727

2828
#include "make_test_thread.h"
2929
#include "test_macros.h"
3030

31-
class Pred
32-
{
33-
int& i_;
34-
public:
35-
explicit Pred(int& i) : i_(i) {}
36-
37-
bool operator()() {return i_ != 0;}
38-
};
39-
40-
std::condition_variable cv;
41-
std::mutex mut;
42-
43-
int test1 = 0;
44-
int test2 = 0;
45-
46-
int runs = 0;
47-
48-
void f()
49-
{
50-
typedef std::chrono::system_clock Clock;
51-
typedef std::chrono::milliseconds milliseconds;
52-
std::unique_lock<std::mutex> lk(mut);
53-
assert(test2 == 0);
54-
test1 = 1;
55-
cv.notify_one();
56-
Clock::time_point t0 = Clock::now();
57-
bool r = cv.wait_for(lk, milliseconds(250), Pred(test2));
58-
((void)r); // Prevent unused warning
59-
Clock::time_point t1 = Clock::now();
60-
if (runs == 0)
61-
{
62-
assert(t1 - t0 < milliseconds(250));
63-
assert(test2 != 0);
64-
}
65-
else
66-
{
67-
assert(t1 - t0 - milliseconds(250) < milliseconds(50));
68-
assert(test2 == 0);
69-
}
70-
++runs;
31+
template <class Function>
32+
std::chrono::microseconds measure(Function f) {
33+
std::chrono::high_resolution_clock::time_point start = std::chrono::high_resolution_clock::now();
34+
f();
35+
std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
36+
return std::chrono::duration_cast<std::chrono::microseconds>(end - start);
7137
}
7238

73-
int main(int, char**)
74-
{
75-
{
76-
std::unique_lock<std::mutex>lk(mut);
77-
std::thread t = support::make_test_thread(f);
78-
assert(test1 == 0);
79-
while (test1 == 0)
80-
cv.wait(lk);
81-
assert(test1 != 0);
82-
test2 = 1;
83-
lk.unlock();
84-
cv.notify_one();
85-
t.join();
86-
}
87-
test1 = 0;
88-
test2 = 0;
89-
{
90-
std::unique_lock<std::mutex>lk(mut);
91-
std::thread t = support::make_test_thread(f);
92-
assert(test1 == 0);
93-
while (test1 == 0)
94-
cv.wait(lk);
95-
assert(test1 != 0);
96-
lk.unlock();
97-
t.join();
98-
}
39+
int main(int, char**) {
40+
// Test unblocking via a call to notify_one() in another thread.
41+
//
42+
// To test this, we set a very long timeout in wait_for() and we try to minimize
43+
// the likelihood that we got awoken by a spurious wakeup by updating the
44+
// likely_spurious flag only immediately before we perform the notification.
45+
{
46+
std::atomic<bool> ready(false);
47+
std::atomic<bool> likely_spurious(true);
48+
auto timeout = std::chrono::seconds(3600);
49+
std::condition_variable cv;
50+
std::mutex mutex;
51+
52+
std::thread t1 = support::make_test_thread([&] {
53+
std::unique_lock<std::mutex> lock(mutex);
54+
auto elapsed = measure([&] {
55+
ready = true;
56+
bool result = cv.wait_for(lock, timeout, [&] { return !likely_spurious; });
57+
assert(result); // return value should be true since we didn't time out
58+
});
59+
assert(elapsed < timeout);
60+
});
61+
62+
std::thread t2 = support::make_test_thread([&] {
63+
while (!ready) {
64+
// spin
65+
}
66+
67+
// Acquire the same mutex as t1. This ensures that the condition variable has started
68+
// waiting (and hence released that mutex).
69+
std::unique_lock<std::mutex> lock(mutex);
70+
71+
likely_spurious = false;
72+
lock.unlock();
73+
cv.notify_one();
74+
});
75+
76+
t2.join();
77+
t1.join();
78+
}
79+
80+
// Test unblocking via a timeout.
81+
//
82+
// To test this, we create a thread that waits on a condition variable with a certain
83+
// timeout, and we never awaken it. The "stop waiting" predicate always returns false,
84+
// which means that we can't get out of the wait via a spurious wakeup.
85+
{
86+
auto timeout = std::chrono::milliseconds(250);
87+
std::condition_variable cv;
88+
std::mutex mutex;
89+
90+
std::thread t1 = support::make_test_thread([&] {
91+
std::unique_lock<std::mutex> lock(mutex);
92+
auto elapsed = measure([&] {
93+
bool result = cv.wait_for(lock, timeout, [] { return false; }); // never stop waiting (until timeout)
94+
assert(!result); // return value should be false since the predicate returns false after the timeout
95+
});
96+
assert(elapsed >= timeout);
97+
});
98+
99+
t1.join();
100+
}
101+
102+
// Test unblocking via a spurious wakeup.
103+
//
104+
// To test this, we set a fairly long timeout in wait_for() and we basically never
105+
// wake up the condition variable. This way, we are hoping to get out of the wait
106+
// via a spurious wakeup.
107+
//
108+
// However, since spurious wakeups are not required to even happen, this test is
109+
// only trying to trigger that code path, but not actually asserting that it is
110+
// taken. In particular, we do need to eventually ensure we get out of the wait
111+
// by standard means, so we actually wake up the thread at the end.
112+
{
113+
std::atomic<bool> ready(false);
114+
std::atomic<bool> awoken(false);
115+
auto timeout = std::chrono::seconds(3600);
116+
std::condition_variable cv;
117+
std::mutex mutex;
118+
119+
std::thread t1 = support::make_test_thread([&] {
120+
std::unique_lock<std::mutex> lock(mutex);
121+
auto elapsed = measure([&] {
122+
ready = true;
123+
bool result = cv.wait_for(lock, timeout, [&] { return true; });
124+
awoken = true;
125+
assert(result); // return value should be true since we didn't time out
126+
});
127+
assert(elapsed < timeout); // can technically fail if t2 never executes and we timeout, but very unlikely
128+
});
129+
130+
std::thread t2 = support::make_test_thread([&] {
131+
while (!ready) {
132+
// spin
133+
}
134+
135+
// Acquire the same mutex as t1. This ensures that the condition variable has started
136+
// waiting (and hence released that mutex).
137+
std::unique_lock<std::mutex> lock(mutex);
138+
lock.unlock();
139+
140+
// Give some time for t1 to be awoken spuriously so that code path is used.
141+
std::this_thread::sleep_for(std::chrono::seconds(1));
142+
143+
// We would want to assert that the thread has been awoken after this time,
144+
// however nothing guarantees us that it ever gets spuriously awoken, so
145+
// we can't really check anything. This is still left here as documentation.
146+
bool woke = awoken.load();
147+
assert(woke || !woke);
148+
149+
// Whatever happened, actually awaken the condition variable to ensure the test
150+
// doesn't keep running until the timeout.
151+
cv.notify_one();
152+
});
153+
154+
t2.join();
155+
t1.join();
156+
}
99157

100158
return 0;
101159
}

0 commit comments

Comments
 (0)