1 | //===----------------------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // UNSUPPORTED: no-threads |
10 | // UNSUPPORTED: c++03, c++11, c++14, c++17 |
11 | // UNSUPPORTED: libcpp-has-no-experimental-stop_token |
12 | // XFAIL: availability-synchronization_library-missing |
13 | |
14 | // <condition_variable> |
15 | |
16 | // class condition_variable_any; |
17 | |
18 | // template<class Lock, class Clock, class Duration, class Predicate> |
19 | // bool wait_until(Lock& lock, stop_token stoken, |
20 | // const chrono::time_point<Clock, Duration>& abs_time, Predicate pred); |
21 | |
22 | #include <cassert> |
23 | #include <chrono> |
24 | #include <concepts> |
25 | #include <condition_variable> |
26 | #include <functional> |
27 | #include <mutex> |
28 | #include <shared_mutex> |
29 | #include <stop_token> |
30 | #include <thread> |
31 | |
32 | #include "helpers.h" |
33 | #include "make_test_thread.h" |
34 | #include "test_macros.h" |
35 | |
36 | template <class Mutex, class Lock> |
37 | void test() { |
38 | using namespace std::chrono_literals; |
39 | const auto oneHourAgo = std::chrono::steady_clock::now() - 1h; |
40 | const auto oneHourLater = std::chrono::steady_clock::now() + 1h; |
41 | |
42 | // stop_requested before hand |
43 | { |
44 | std::stop_source ss; |
45 | std::condition_variable_any cv; |
46 | Mutex mutex; |
47 | Lock lock{mutex}; |
48 | ss.request_stop(); |
49 | ElapsedTimeCheck check(1min); |
50 | |
51 | // [Note 4: The returned value indicates whether the predicate evaluated to true |
52 | // regardless of whether the timeout was triggered or a stop request was made.] |
53 | std::same_as<bool> auto r1 = cv.wait_until(lock, ss.get_token(), oneHourAgo, []() { return false; }); |
54 | assert(!r1); |
55 | |
56 | std::same_as<bool> auto r2 = cv.wait_until(lock, ss.get_token(), oneHourLater, []() { return false; }); |
57 | assert(!r2); |
58 | |
59 | std::same_as<bool> auto r3 = cv.wait_until(lock, ss.get_token(), oneHourAgo, []() { return true; }); |
60 | assert(r3); |
61 | |
62 | std::same_as<bool> auto r4 = cv.wait_until(lock, ss.get_token(), oneHourLater, []() { return true; }); |
63 | assert(r4); |
64 | |
65 | // Postconditions: lock is locked by the calling thread. |
66 | assert(lock.owns_lock()); |
67 | } |
68 | |
69 | // no stop request, pred was true |
70 | { |
71 | std::stop_source ss; |
72 | std::condition_variable_any cv; |
73 | Mutex mutex; |
74 | Lock lock{mutex}; |
75 | ElapsedTimeCheck check(1min); |
76 | |
77 | std::same_as<bool> auto r1 = cv.wait_until(lock, ss.get_token(), oneHourAgo, []() { return true; }); |
78 | assert(r1); |
79 | |
80 | std::same_as<bool> auto r2 = cv.wait_until(lock, ss.get_token(), oneHourLater, []() { return true; }); |
81 | assert(r2); |
82 | } |
83 | |
84 | // no stop request, pred was false, abs_time was in the past |
85 | { |
86 | std::stop_source ss; |
87 | std::condition_variable_any cv; |
88 | Mutex mutex; |
89 | Lock lock{mutex}; |
90 | ElapsedTimeCheck check(1min); |
91 | |
92 | std::same_as<bool> auto r1 = cv.wait_until(lock, ss.get_token(), oneHourAgo, []() { return false; }); |
93 | assert(!r1); |
94 | } |
95 | |
96 | // no stop request, pred was false until timeout |
97 | { |
98 | std::stop_source ss; |
99 | std::condition_variable_any cv; |
100 | Mutex mutex; |
101 | Lock lock{mutex}; |
102 | |
103 | auto oldTime = std::chrono::steady_clock::now(); |
104 | |
105 | std::same_as<bool> auto r1 = |
106 | cv.wait_until(lock, ss.get_token(), oldTime + std::chrono::milliseconds(2), [&]() { return false; }); |
107 | |
108 | assert((std::chrono::steady_clock::now() - oldTime) >= std::chrono::milliseconds(2)); |
109 | assert(!r1); |
110 | } |
111 | |
112 | // no stop request, pred was false, changed to true before timeout |
113 | { |
114 | std::stop_source ss; |
115 | std::condition_variable_any cv; |
116 | Mutex mutex; |
117 | Lock lock{mutex}; |
118 | |
119 | bool flag = false; |
120 | auto thread = support::make_test_thread([&]() { |
121 | std::this_thread::sleep_for(std::chrono::milliseconds(2)); |
122 | std::unique_lock<Mutex> lock2{mutex}; |
123 | flag = true; |
124 | cv.notify_all(); |
125 | }); |
126 | |
127 | ElapsedTimeCheck check(10min); |
128 | |
129 | std::same_as<bool> auto r1 = cv.wait_until(lock, ss.get_token(), oneHourLater, [&]() { return flag; }); |
130 | assert(flag); |
131 | assert(r1); |
132 | |
133 | thread.join(); |
134 | } |
135 | |
136 | // stop request comes while waiting |
137 | { |
138 | std::stop_source ss; |
139 | std::condition_variable_any cv; |
140 | Mutex mutex; |
141 | Lock lock{mutex}; |
142 | |
143 | std::atomic_bool start = false; |
144 | std::atomic_bool done = false; |
145 | auto thread = support::make_test_thread([&]() { |
146 | start.wait(false); |
147 | ss.request_stop(); |
148 | |
149 | while (!done) { |
150 | cv.notify_all(); |
151 | std::this_thread::sleep_for(std::chrono::milliseconds(2)); |
152 | } |
153 | }); |
154 | |
155 | ElapsedTimeCheck check(10min); |
156 | |
157 | std::same_as<bool> auto r = cv.wait_until(lock, ss.get_token(), oneHourLater, [&]() { |
158 | start.store(true); |
159 | start.notify_all(); |
160 | return false; |
161 | }); |
162 | assert(!r); |
163 | done = true; |
164 | thread.join(); |
165 | |
166 | assert(lock.owns_lock()); |
167 | } |
168 | |
169 | // #76807 Hangs in std::condition_variable_any when used with std::stop_token |
170 | { |
171 | class MyThread { |
172 | public: |
173 | MyThread() { |
174 | thread_ = support::make_test_jthread([this](std::stop_token st) { |
175 | while (!st.stop_requested()) { |
176 | std::unique_lock lock{m_}; |
177 | cv_.wait_until(lock, st, std::chrono::steady_clock::now() + std::chrono::hours(1), [] { return false; }); |
178 | } |
179 | }); |
180 | } |
181 | |
182 | private: |
183 | std::mutex m_; |
184 | std::condition_variable_any cv_; |
185 | std::jthread thread_; |
186 | }; |
187 | |
188 | ElapsedTimeCheck check(10min); |
189 | |
190 | [[maybe_unused]] MyThread my_thread; |
191 | } |
192 | |
193 | // request_stop potentially in-between check and wait |
194 | { |
195 | std::stop_source ss; |
196 | std::condition_variable_any cv; |
197 | Mutex mutex; |
198 | Lock lock{mutex}; |
199 | |
200 | std::atomic_bool pred_started = false; |
201 | std::atomic_bool request_stop_called = false; |
202 | auto thread = support::make_test_thread([&]() { |
203 | pred_started.wait(false); |
204 | ss.request_stop(); |
205 | request_stop_called.store(true); |
206 | request_stop_called.notify_all(); |
207 | }); |
208 | |
209 | ElapsedTimeCheck check(10min); |
210 | |
211 | std::same_as<bool> auto r = cv.wait_until(lock, ss.get_token(), oneHourLater, [&]() { |
212 | pred_started.store(true); |
213 | pred_started.notify_all(); |
214 | request_stop_called.wait(false); |
215 | return false; |
216 | }); |
217 | assert(!r); |
218 | thread.join(); |
219 | |
220 | assert(lock.owns_lock()); |
221 | } |
222 | |
223 | #if !defined(TEST_HAS_NO_EXCEPTIONS) |
224 | // Throws: Any exception thrown by pred. |
225 | { |
226 | std::stop_source ss; |
227 | std::condition_variable_any cv; |
228 | Mutex mutex; |
229 | Lock lock{mutex}; |
230 | |
231 | try { |
232 | cv.wait_until(lock, ss.get_token(), oneHourLater, []() -> bool { throw 5; }); |
233 | assert(false); |
234 | } catch (int i) { |
235 | assert(i == 5); |
236 | } |
237 | } |
238 | #endif //!defined(TEST_HAS_NO_EXCEPTIONS) |
239 | } |
240 | |
241 | int main(int, char**) { |
242 | test<std::mutex, std::unique_lock<std::mutex>>(); |
243 | test<std::shared_mutex, std::shared_lock<std::shared_mutex>>(); |
244 | |
245 | return 0; |
246 | } |
247 | |