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