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