diff --git a/libcxx/include/condition_variable b/libcxx/include/condition_variable --- a/libcxx/include/condition_variable +++ b/libcxx/include/condition_variable @@ -100,6 +100,18 @@ wait_for(Lock& lock, const chrono::duration& rel_time, Predicate pred); + + // [thread.condvarany.intwait], interruptible waits + template + bool wait(Lock& lock, stop_token stoken, Predicate pred); // since C++20 + + template + bool wait_until(Lock& lock, stop_token stoken, + const chrono::time_point& abs_time, Predicate pred); // since C++20 + + template + bool wait_for(Lock& lock, stop_token stoken, + const chrono::duration& rel_time, Predicate pred); // since C++20 }; } // std @@ -107,6 +119,7 @@ */ #include <__assert> // all public C++ headers provide the assertion handler +#include <__availability> #include <__chrono/duration.h> #include <__chrono/steady_clock.h> #include <__chrono/time_point.h> @@ -118,6 +131,7 @@ #include <__mutex/mutex.h> #include <__mutex/tag_types.h> #include <__mutex/unique_lock.h> +#include <__stop_token/stop_token.h> #include <__utility/move.h> #include @@ -174,6 +188,21 @@ wait_for(_Lock& __lock, const chrono::duration<_Rep, _Period>& __d, _Predicate __pred); + +#if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + + template + _LIBCPP_HIDE_FROM_ABI bool wait(_Lock& __lock, stop_token __stoken, _Predicate __pred); + + template + _LIBCPP_HIDE_FROM_ABI bool wait_until(_Lock& __lock, stop_token __stoken, + const chrono::time_point<_Clock, _Duration>& __abs_time, _Predicate __pred); + + template + _LIBCPP_HIDE_FROM_ABI bool wait_for(_Lock& __lock, stop_token __stoken, + const chrono::duration<_Rep, _Period>& __rel_time, _Predicate __pred); + +#endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) }; inline @@ -269,6 +298,38 @@ _VSTD::move(__pred)); } +#if _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + +template +bool condition_variable_any::wait(_Lock& __lock, stop_token __stoken, _Predicate __pred) { + while (!__stoken.stop_requested()) { + if (__pred()) + return true; + wait(__lock); + } + return __pred(); +} + +template +bool condition_variable_any::wait_until( + _Lock& __lock, stop_token __stoken, const chrono::time_point<_Clock, _Duration>& __abs_time, _Predicate __pred) { + while (!__stoken.stop_requested()) { + if (__pred()) + return true; + if (wait_until(__lock, __abs_time) == cv_status::timeout) + return __pred(); + } + return __pred(); +} + +template +bool condition_variable_any::wait_for( + _Lock& __lock, stop_token __stoken, const chrono::duration<_Rep, _Period>& __rel_time, _Predicate __pred) { + return wait_until(__lock, std::move(__stoken), chrono::steady_clock::now() + __rel_time, std::move(__pred)); +} + +#endif // _LIBCPP_STD_VER >= 20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + _LIBCPP_EXPORTED_FROM_ABI void notify_all_at_thread_exit(condition_variable&, unique_lock); _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv --- a/libcxx/test/libcxx/transitive_includes/cxx03.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv @@ -181,6 +181,7 @@ condition_variable stdexcept condition_variable string condition_variable system_error +condition_variable thread condition_variable type_traits condition_variable typeinfo condition_variable version diff --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv --- a/libcxx/test/libcxx/transitive_includes/cxx11.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv @@ -182,6 +182,7 @@ condition_variable stdexcept condition_variable string condition_variable system_error +condition_variable thread condition_variable type_traits condition_variable typeinfo condition_variable version diff --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv --- a/libcxx/test/libcxx/transitive_includes/cxx14.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv @@ -182,6 +182,7 @@ condition_variable stdexcept condition_variable string condition_variable system_error +condition_variable thread condition_variable type_traits condition_variable typeinfo condition_variable version diff --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv --- a/libcxx/test/libcxx/transitive_includes/cxx17.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv @@ -182,6 +182,7 @@ condition_variable stdexcept condition_variable string condition_variable system_error +condition_variable thread condition_variable type_traits condition_variable typeinfo condition_variable version diff --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv --- a/libcxx/test/libcxx/transitive_includes/cxx20.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv @@ -188,6 +188,7 @@ condition_variable stdexcept condition_variable string condition_variable system_error +condition_variable thread condition_variable type_traits condition_variable typeinfo condition_variable version diff --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv --- a/libcxx/test/libcxx/transitive_includes/cxx23.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv @@ -118,8 +118,11 @@ complex version concepts cstddef concepts version +condition_variable atomic condition_variable cerrno condition_variable cstddef +condition_variable cstdint +condition_variable cstring condition_variable ctime condition_variable iosfwd condition_variable limits @@ -127,6 +130,7 @@ condition_variable ratio condition_variable stdexcept condition_variable string +condition_variable thread condition_variable typeinfo condition_variable version coroutine compare diff --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv --- a/libcxx/test/libcxx/transitive_includes/cxx26.csv +++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv @@ -118,8 +118,11 @@ complex version concepts cstddef concepts version +condition_variable atomic condition_variable cerrno condition_variable cstddef +condition_variable cstdint +condition_variable cstring condition_variable ctime condition_variable iosfwd condition_variable limits @@ -127,6 +130,7 @@ condition_variable ratio condition_variable stdexcept condition_variable string +condition_variable thread condition_variable typeinfo condition_variable version coroutine compare diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_token_pred.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_token_pred.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_token_pred.pass.cpp @@ -0,0 +1,176 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-experimental-stop_token +// XFAIL: availability-synchronization_library-missing + +// + +// class condition_variable_any; + +// template +// bool wait_for(Lock& lock, stop_token stoken, +// const chrono::duration& rel_time, Predicate pred); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "make_test_thread.h" +#include "test_macros.h" + +template +void test() { + using namespace std::chrono_literals; + + // stop_requested before hand + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + ss.request_stop(); + + // [Note 4: The returned value indicates whether the predicate evaluated to true + // regardless of whether the timeout was triggered or a stop request was made.] + std::same_as auto r1 = cv.wait_for(lock, ss.get_token(), -1h, []() { return false; }); + assert(!r1); + + std::same_as auto r2 = cv.wait_for(lock, ss.get_token(), 1h, []() { return false; }); + assert(!r2); + + std::same_as auto r3 = cv.wait_for(lock, ss.get_token(), -1h, []() { return true; }); + assert(r3); + + std::same_as auto r4 = cv.wait_for(lock, ss.get_token(), 1h, []() { return true; }); + assert(r4); + + // Postconditions: lock is locked by the calling thread. + assert(lock.owns_lock()); + } + + // no stop request, pred was true + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + std::same_as auto r1 = cv.wait_for(lock, ss.get_token(), -1h, []() { return true; }); + assert(r1); + + std::same_as auto r2 = cv.wait_for(lock, ss.get_token(), 1h, []() { return true; }); + assert(r2); + } + + // no stop request, pred was false, abs_time was in the past + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + std::same_as auto r1 = cv.wait_for(lock, ss.get_token(), -1h, []() { return false; }); + assert(!r1); + } + + // no stop request, pred was false until timeout + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + auto old_time = std::chrono::steady_clock::now(); + + std::same_as auto r1 = cv.wait_for(lock, ss.get_token(), 2ms, [&]() { return false; }); + + assert((std::chrono::steady_clock::now() - old_time) >= 2ms); + assert(!r1); + } + + // no stop request, pred was false, changed to true before timeout + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + bool flag = false; + auto thread = support::make_test_thread([&]() { + std::this_thread::sleep_for(2ms); + Lock lock2{mutex}; + flag = true; + cv.notify_all(); + }); + + std::same_as auto r1 = cv.wait_for(lock, ss.get_token(), 1h, [&]() { return flag; }); + assert(flag); + assert(r1); + + thread.join(); + } + + // stop request comes while waiting + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + std::atomic_bool done = false; + auto thread = support::make_test_thread([&]() { + std::this_thread::sleep_for(2ms); + ss.request_stop(); + + while (!done) { + cv.notify_all(); + std::this_thread::sleep_for(2ms); + } + }); + + std::same_as auto r = cv.wait_for(lock, ss.get_token(), 1h, [&]() { return false; }); + assert(!r); + done = true; + thread.join(); + + assert(lock.owns_lock()); + } + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + // Throws: Any exception thrown by pred. + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + try { + cv.wait_for(lock, ss.get_token(), 1h, []() -> bool { throw 5; }); + assert(false); + } catch (int i) { + assert(i == 5); + } + } +#endif //!defined(TEST_HAS_NO_EXCEPTIONS) +} + +int main(int, char**) { + test>(); + test>(); + + return 0; +} diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_terminates.sh.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_terminates.sh.cpp --- a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_terminates.sh.cpp +++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_terminates.sh.cpp @@ -20,6 +20,9 @@ // RUN: %{run} 4 // RUN: %{run} 5 // RUN: %{run} 6 +// RUN: %{run} 7 +// RUN: %{run} 8 +// RUN: %{run} 9 // ----------------------------------------------------------------------------- // Overview @@ -34,6 +37,9 @@ // 4. void wait_for(Lock& lock, Duration, Pred); // 5. void wait_until(Lock& lock, TimePoint); // 6. void wait_until(Lock& lock, TimePoint, Pred); +// 7. bool wait(Lock& lock, stop_token stoken, Predicate pred); +// 8. bool wait_for(Lock& lock, stop_token stoken, Duration, Predicate pred); +// 9. bool wait_until(Lock& lock, stop_token stoken, TimePoint, Predicate pred); // // Plan // 1 Create a mutex type, 'ThrowingMutex', that throws when the lock is acquired @@ -62,9 +68,11 @@ #include #include #include +#include #include #include "make_test_thread.h" +#include "test_macros.h" void my_terminate() { std::_Exit(0); // Use _Exit to prevent cleanup from taking place. @@ -115,7 +123,7 @@ int main(int argc, char **argv) { assert(argc == 2); int id = std::stoi(argv[1]); - assert(id >= 1 && id <= 6); + assert(id >= 1 && id <= 9); std::set_terminate(my_terminate); // set terminate after std::stoi because it can throw. MS wait(250); try { @@ -129,6 +137,16 @@ case 4: cv.wait_for(mut, wait, pred_function); break; case 5: cv.wait_until(mut, Clock::now() + wait); break; case 6: cv.wait_until(mut, Clock::now() + wait, pred_function); break; +#if TEST_STD_VER >=20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) + case 7: cv.wait(mut, std::stop_source{}.get_token(), pred_function); break; + case 8: cv.wait_for(mut, std::stop_source{}.get_token(), wait, pred_function); break; + case 9: cv.wait_until(mut, std::stop_source{}.get_token(), Clock::now() + wait, pred_function); break; +#else + case 7: + case 8: + case 9: + return 0; +#endif //TEST_STD_VER >=20 && !defined(_LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN) default: assert(false); } } catch (...) {} diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_token_pred.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_token_pred.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_token_pred.pass.cpp @@ -0,0 +1,128 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-experimental-stop_token +// XFAIL: availability-synchronization_library-missing + +// + +// class condition_variable_any; + +// template +// bool wait(Lock& lock, stop_token stoken, Predicate pred); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "make_test_thread.h" +#include "test_macros.h" + +template +void test() { + // stop_requested before hand + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + ss.request_stop(); + + // [Note 1: The returned value indicates whether the predicate evaluated to true regardless of whether there was a stop request.] + std::same_as auto r1 = cv.wait(lock, ss.get_token(), []() { return false; }); + assert(!r1); + + std::same_as auto r2 = cv.wait(lock, ss.get_token(), []() { return true; }); + assert(r2); + + // Postconditions: lock is locked by the calling thread. + assert(lock.owns_lock()); + } + + // no stop request + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + std::same_as auto r1 = cv.wait(lock, ss.get_token(), []() { return true; }); + assert(r1); + + bool flag = false; + auto thread = support::make_test_thread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + Lock lock2{mutex}; + flag = true; + cv.notify_all(); + }); + + std::same_as auto r2 = cv.wait(lock, ss.get_token(), [&]() { return flag; }); + assert(flag); + assert(r2); + thread.join(); + + assert(lock.owns_lock()); + } + + // stop request comes while waiting + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + std::atomic_bool done = false; + auto thread = support::make_test_thread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + ss.request_stop(); + + while (!done) { + cv.notify_all(); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + }); + + std::same_as auto r = cv.wait(lock, ss.get_token(), [&]() { return false; }); + assert(!r); + done = true; + thread.join(); + + assert(lock.owns_lock()); + } + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + // Throws: Any exception thrown by pred. + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + try { + cv.wait(lock, ss.get_token(), []() -> bool { throw 5; }); + assert(false); + } catch (int i) { + assert(i == 5); + } + } +#endif //!defined(TEST_HAS_NO_EXCEPTIONS) +} + +int main(int, char**) { + test>(); + test>(); + + return 0; +} diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_until_token_pred.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_until_token_pred.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_until_token_pred.pass.cpp @@ -0,0 +1,178 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// UNSUPPORTED: no-threads +// UNSUPPORTED: c++03, c++11, c++14, c++17 +// UNSUPPORTED: libcpp-has-no-experimental-stop_token +// XFAIL: availability-synchronization_library-missing + +// + +// class condition_variable_any; + +// template +// bool wait_until(Lock& lock, stop_token stoken, +// const chrono::time_point& abs_time, Predicate pred); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "make_test_thread.h" +#include "test_macros.h" + +template +void test() { + const auto past = std::chrono::steady_clock::now() - std::chrono::hours(1); + const auto future = std::chrono::steady_clock::now() + std::chrono::hours(1); + + // stop_requested before hand + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + ss.request_stop(); + + // [Note 4: The returned value indicates whether the predicate evaluated to true + // regardless of whether the timeout was triggered or a stop request was made.] + std::same_as auto r1 = cv.wait_until(lock, ss.get_token(), past, []() { return false; }); + assert(!r1); + + std::same_as auto r2 = cv.wait_until(lock, ss.get_token(), future, []() { return false; }); + assert(!r2); + + std::same_as auto r3 = cv.wait_until(lock, ss.get_token(), past, []() { return true; }); + assert(r3); + + std::same_as auto r4 = cv.wait_until(lock, ss.get_token(), future, []() { return true; }); + assert(r4); + + // Postconditions: lock is locked by the calling thread. + assert(lock.owns_lock()); + } + + // no stop request, pred was true + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + std::same_as auto r1 = cv.wait_until(lock, ss.get_token(), past, []() { return true; }); + assert(r1); + + std::same_as auto r2 = cv.wait_until(lock, ss.get_token(), future, []() { return true; }); + assert(r2); + } + + // no stop request, pred was false, abs_time was in the past + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + std::same_as auto r1 = cv.wait_until(lock, ss.get_token(), past, []() { return false; }); + assert(!r1); + } + + // no stop request, pred was false until timeout + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + auto oldTime = std::chrono::steady_clock::now(); + + std::same_as auto r1 = + cv.wait_until(lock, ss.get_token(), oldTime + std::chrono::milliseconds(2), [&]() { return false; }); + + assert((std::chrono::steady_clock::now() - oldTime) >= std::chrono::milliseconds(2)); + assert(!r1); + } + + // no stop request, pred was false, changed to true before timeout + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + bool flag = false; + auto thread = support::make_test_thread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + Lock lock2{mutex}; + flag = true; + cv.notify_all(); + }); + + std::same_as auto r1 = cv.wait_until(lock, ss.get_token(), future, [&]() { return flag; }); + assert(flag); + assert(r1); + + thread.join(); + } + + // stop request comes while waiting + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + std::atomic_bool done = false; + auto thread = support::make_test_thread([&]() { + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + ss.request_stop(); + + while (!done) { + cv.notify_all(); + std::this_thread::sleep_for(std::chrono::milliseconds(2)); + } + }); + + std::same_as auto r = cv.wait_until(lock, ss.get_token(), future, [&]() { return false; }); + assert(!r); + done = true; + thread.join(); + + assert(lock.owns_lock()); + } + +#if !defined(TEST_HAS_NO_EXCEPTIONS) + // Throws: Any exception thrown by pred. + { + std::stop_source ss; + std::condition_variable_any cv; + Mutex mutex; + Lock lock{mutex}; + + try { + cv.wait_until(lock, ss.get_token(), future, []() -> bool { throw 5; }); + assert(false); + } catch (int i) { + assert(i == 5); + } + } +#endif //!defined(TEST_HAS_NO_EXCEPTIONS) +} + +int main(int, char**) { + test>(); + test>(); + + return 0; +}