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 // XFAIL: availability-synchronization_library-missing
12 
13 // <condition_variable>
14 
15 // class condition_variable_any;
16 
17 // template<class Lock, class Predicate>
18 //   bool wait(Lock& lock, stop_token stoken, Predicate pred);
19 
20 #include <atomic>
21 #include <cassert>
22 #include <concepts>
23 #include <condition_variable>
24 #include <functional>
25 #include <mutex>
26 #include <shared_mutex>
27 #include <stop_token>
28 #include <thread>
29 
30 #include "make_test_thread.h"
31 #include "test_macros.h"
32 
33 template <class Mutex, class Lock>
34 void test() {
35   // stop_requested before hand
36   {
37     std::stop_source ss;
38     std::condition_variable_any cv;
39     Mutex mutex;
40     Lock lock{mutex};
41     ss.request_stop();
42 
43     // [Note 1: The returned value indicates whether the predicate evaluated to true regardless of whether there was a stop request.]
44     std::same_as<bool> auto r1 = cv.wait(lock, ss.get_token(), []() { return false; });
45     assert(!r1);
46 
47     std::same_as<bool> auto r2 = cv.wait(lock, ss.get_token(), []() { return true; });
48     assert(r2);
49 
50     // Postconditions: lock is locked by the calling thread.
51     assert(lock.owns_lock());
52   }
53 
54   // no stop request
55   {
56     std::stop_source ss;
57     std::condition_variable_any cv;
58     Mutex mutex;
59     Lock lock{mutex};
60     std::same_as<bool> auto r1 = cv.wait(lock, ss.get_token(), []() { return true; });
61     assert(r1);
62 
63     bool flag   = false;
64     auto thread = support::make_test_thread([&]() {
65       std::this_thread::sleep_for(std::chrono::milliseconds(2));
66       std::unique_lock<Mutex> lock2{mutex};
67       flag = true;
68       cv.notify_all();
69     });
70 
71     std::same_as<bool> auto r2 = cv.wait(lock, ss.get_token(), [&]() { return flag; });
72     assert(flag);
73     assert(r2);
74     thread.join();
75 
76     assert(lock.owns_lock());
77   }
78 
79   // stop request comes while waiting
80   {
81     std::stop_source ss;
82     std::condition_variable_any cv;
83     Mutex mutex;
84     Lock lock{mutex};
85 
86     std::atomic_bool start = false;
87     std::atomic_bool done  = false;
88     auto thread            = support::make_test_thread([&]() {
89       start.wait(false);
90       ss.request_stop();
91 
92       while (!done) {
93         cv.notify_all();
94         std::this_thread::sleep_for(std::chrono::milliseconds(2));
95       }
96     });
97 
98     std::same_as<bool> auto r = cv.wait(lock, ss.get_token(), [&]() {
99       start.store(true);
100       start.notify_all();
101       return false;
102     });
103     assert(!r);
104     done = true;
105     thread.join();
106 
107     assert(lock.owns_lock());
108   }
109 
110   // #76807 Hangs in std::condition_variable_any when used with std::stop_token
111   {
112     class MyThread {
113     public:
114       MyThread() {
115         thread_ = support::make_test_jthread([this](std::stop_token st) {
116           while (!st.stop_requested()) {
117             std::unique_lock lock{m_};
118             cv_.wait(lock, st, [] { return false; });
119           }
120         });
121       }
122 
123     private:
124       std::mutex m_;
125       std::condition_variable_any cv_;
126       std::jthread thread_;
127     };
128 
129     [[maybe_unused]] MyThread my_thread;
130   }
131 
132   // request_stop potentially in-between check and wait
133   {
134     std::stop_source ss;
135     std::condition_variable_any cv;
136     Mutex mutex;
137     Lock lock{mutex};
138 
139     std::atomic_bool pred_started        = false;
140     std::atomic_bool request_stop_called = false;
141     auto thread                          = support::make_test_thread([&]() {
142       pred_started.wait(false);
143       ss.request_stop();
144       request_stop_called.store(true);
145       request_stop_called.notify_all();
146     });
147 
148     std::same_as<bool> auto r = cv.wait(lock, ss.get_token(), [&]() {
149       pred_started.store(true);
150       pred_started.notify_all();
151       request_stop_called.wait(false);
152       return false;
153     });
154     assert(!r);
155     thread.join();
156 
157     assert(lock.owns_lock());
158   }
159 
160 #if !defined(TEST_HAS_NO_EXCEPTIONS)
161   // Throws: Any exception thrown by pred.
162   {
163     std::stop_source ss;
164     std::condition_variable_any cv;
165     Mutex mutex;
166     Lock lock{mutex};
167 
168     try {
169       cv.wait(lock, ss.get_token(), []() -> bool { throw 5; });
170       assert(false);
171     } catch (int i) {
172       assert(i == 5);
173     }
174   }
175 #endif //!defined(TEST_HAS_NO_EXCEPTIONS)
176 }
177 
178 int main(int, char**) {
179   test<std::mutex, std::unique_lock<std::mutex>>();
180   test<std::shared_mutex, std::shared_lock<std::shared_mutex>>();
181 
182   return 0;
183 }
184