[libcxx-commits] [libcxx] [libc++][chrono][threading] Implement LWG 3504: `condition_variable::wait_for` is overspecified (PR #222443)
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Sep 9 13:52:56 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Vishank Singh (VishankSingh)
<details>
<summary>Changes</summary>
Use `ceil<steady_clock::duration>` for relative-to-absolute conversion to avoid floating-point precision loss. Update all `wait_for` overloads and add tests.
Fixes #<!-- -->189807
---
Full diff: https://github.com/llvm/llvm-project/pull/222443.diff
8 Files Affected:
- (modified) libcxx/docs/Status/Cxx26Issues.csv (+1-1)
- (modified) libcxx/include/__condition_variable/condition_variable.h (+8-24)
- (modified) libcxx/include/condition_variable (+3-3)
- (modified) libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for.pass.cpp (+15)
- (modified) libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for_pred.pass.cpp (+15)
- (modified) libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for.pass.cpp (+15)
- (modified) libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_pred.pass.cpp (+15)
- (modified) libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_token_pred.pass.cpp (+17)
``````````diff
diff --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index b98f3ee12f3af..252f59a599dbc 100644
--- a/libcxx/docs/Status/Cxx26Issues.csv
+++ b/libcxx/docs/Status/Cxx26Issues.csv
@@ -262,7 +262,7 @@
"","","","","","",""
"`LWG2414 <https://wg21.link/LWG2414>`__","Member function reentrancy should be implementation-defined","2026-03 (Croydon)","","","`#189805 <https://github.com/llvm/llvm-project/issues/189805>`__",""
"`LWG2746 <https://wg21.link/LWG2746>`__","Inconsistency between requirements for ``emplace`` between ``optional`` and ``variant``","2026-03 (Croydon)","","","`#189806 <https://github.com/llvm/llvm-project/issues/189806>`__",""
-"`LWG3504 <https://wg21.link/LWG3504>`__","``condition_variable::wait_for`` is overspecified","2026-03 (Croydon)","","","`#189807 <https://github.com/llvm/llvm-project/issues/189807>`__",""
+"`LWG3504 <https://wg21.link/LWG3504>`__","``condition_variable::wait_for`` is overspecified","2026-03 (Croydon)","|Complete|","24","`#189807 <https://github.com/llvm/llvm-project/issues/189807>`__",""
"`LWG3599 <https://wg21.link/LWG3599>`__","The ``const`` overload of ``lazy_split_view::begin`` should be constrained by ``const Pattern``","2026-03 (Croydon)","|Complete|","24","`#189808 <https://github.com/llvm/llvm-project/issues/189808>`__",""
"`LWG3662 <https://wg21.link/LWG3662>`__","``basic_string::append/assign(NTBS, pos, n)`` suboptimal","2026-03 (Croydon)","|Complete|","23","`#189809 <https://github.com/llvm/llvm-project/issues/189809>`__",""
"`LWG3777 <https://wg21.link/LWG3777>`__","Common ``cartesian_product_view`` produces an invalid range if the first range is input and one of the ranges is empty","2026-03 (Croydon)","","","`#189810 <https://github.com/llvm/llvm-project/issues/189810>`__",""
diff --git a/libcxx/include/__condition_variable/condition_variable.h b/libcxx/include/__condition_variable/condition_variable.h
index e15113ced5758..470ec749c84cf 100644
--- a/libcxx/include/__condition_variable/condition_variable.h
+++ b/libcxx/include/__condition_variable/condition_variable.h
@@ -40,6 +40,12 @@ _LIBCPP_BEGIN_EXPLICIT_ABI_ANNOTATIONS
_LIBCPP_DECLARE_STRONG_ENUM(cv_status){no_timeout, timeout};
_LIBCPP_DECLARE_STRONG_ENUM_EPILOG(cv_status)
+template <class _Duration>
+inline _LIBCPP_HIDE_FROM_ABI chrono::steady_clock::time_point __rel_to_abs(const _Duration& __rel_time) {
+ using namespace chrono;
+ return steady_clock::now() + ceil<steady_clock::duration>(__rel_time);
+}
+
template <class _Rep, class _Period, __enable_if_t<is_floating_point<_Rep>::value, int> = 0>
inline _LIBCPP_HIDE_FROM_ABI chrono::nanoseconds __safe_nanosecond_cast(chrono::duration<_Rep, _Period> __d) {
using namespace chrono;
@@ -141,29 +147,7 @@ class _LIBCPP_EXPORTED_FROM_ABI condition_variable {
template <class _Rep, class _Period>
_LIBCPP_HIDE_FROM_ABI cv_status wait_for(unique_lock<mutex>& __lk, const chrono::duration<_Rep, _Period>& __d) {
- using namespace chrono;
- if (__d <= __d.zero())
- return cv_status::timeout;
- using __ns_rep = nanoseconds::rep;
- steady_clock::time_point __c_now = steady_clock::now();
-
-# if _LIBCPP_HAS_COND_CLOCKWAIT
- using __clock_tp_ns = time_point<steady_clock, nanoseconds>;
- __ns_rep __now_count_ns = std::__safe_nanosecond_cast(__c_now.time_since_epoch()).count();
-# else
- using __clock_tp_ns = time_point<system_clock, nanoseconds>;
- __ns_rep __now_count_ns = std::__safe_nanosecond_cast(system_clock::now().time_since_epoch()).count();
-# endif
-
- __ns_rep __d_ns_count = std::__safe_nanosecond_cast(__d).count();
-
- if (__now_count_ns > numeric_limits<__ns_rep>::max() - __d_ns_count) {
- __do_timed_wait(__lk, __clock_tp_ns::max());
- } else {
- __do_timed_wait(__lk, __clock_tp_ns(nanoseconds(__now_count_ns + __d_ns_count)));
- }
-
- return steady_clock::now() - __c_now < __d ? cv_status::no_timeout : cv_status::timeout;
+ return wait_until(__lk, std::__rel_to_abs(__d));
}
template <class _Rep, class _Period, class _Predicate>
@@ -191,7 +175,7 @@ class _LIBCPP_EXPORTED_FROM_ABI condition_variable {
template <class _Rep, class _Period, class _Predicate>
inline bool
condition_variable::wait_for(unique_lock<mutex>& __lk, const chrono::duration<_Rep, _Period>& __d, _Predicate __pred) {
- return wait_until(__lk, chrono::steady_clock::now() + __d, std::move(__pred));
+ return wait_until(__lk, std::__rel_to_abs(__d), std::move(__pred));
}
# if _LIBCPP_HAS_COND_CLOCKWAIT
diff --git a/libcxx/include/condition_variable b/libcxx/include/condition_variable
index 2bb4c1b0a8ad9..9148041f8f2b4 100644
--- a/libcxx/include/condition_variable
+++ b/libcxx/include/condition_variable
@@ -249,13 +249,13 @@ condition_variable_any::wait_until(_Lock& __lock, const chrono::time_point<_Cloc
template <class _Lock, class _Rep, class _Period>
inline cv_status condition_variable_any::wait_for(_Lock& __lock, const chrono::duration<_Rep, _Period>& __d) {
- return wait_until(__lock, chrono::steady_clock::now() + __d);
+ return wait_until(__lock, std::__rel_to_abs(__d));
}
template <class _Lock, class _Rep, class _Period, class _Predicate>
inline bool
condition_variable_any::wait_for(_Lock& __lock, const chrono::duration<_Rep, _Period>& __d, _Predicate __pred) {
- return wait_until(__lock, chrono::steady_clock::now() + __d, std::move(__pred));
+ return wait_until(__lock, std::__rel_to_abs(__d), std::move(__pred));
}
# if _LIBCPP_STD_VER >= 20
@@ -336,7 +336,7 @@ bool condition_variable_any::wait_until(
template <class _Lock, class _Rep, class _Period, class _Predicate>
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));
+ return wait_until(__lock, std::move(__stoken), std::__rel_to_abs(__rel_time), std::move(__pred));
}
# endif // _LIBCPP_STD_VER >= 20
diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for.pass.cpp
index 6a054f74b9fb9..df0743c48f236 100644
--- a/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for.pass.cpp
+++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for.pass.cpp
@@ -105,5 +105,20 @@ int main(int, char**) {
t1.join();
}
+ // Regression test for LWG 3504: wait_for() with a floating-point
+ {
+ std::condition_variable cv;
+ std::mutex mutex;
+ std::unique_lock<std::mutex> lock(mutex);
+
+ auto start = std::chrono::steady_clock::now();
+ std::cv_status status = cv.wait_for(lock, std::chrono::duration<float>(0.25f));
+ auto elapsed = std::chrono::steady_clock::now() - start;
+
+ assert(status == std::cv_status::timeout);
+ assert(elapsed >= std::chrono::milliseconds(200));
+ assert(elapsed < std::chrono::milliseconds(600));
+ }
+
return 0;
}
diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for_pred.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for_pred.pass.cpp
index 76fc7393bc8fc..2497c3dceb18d 100644
--- a/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for_pred.pass.cpp
+++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for_pred.pass.cpp
@@ -155,5 +155,20 @@ int main(int, char**) {
t1.join();
}
+ // Regression test for LWG 3504: wait_for() with a floating-point
+ {
+ std::condition_variable cv;
+ std::mutex mutex;
+ std::unique_lock<std::mutex> lock(mutex);
+
+ auto start = std::chrono::steady_clock::now();
+ bool result = cv.wait_for(lock, std::chrono::duration<float>(0.25f), [] { return false; });
+ auto elapsed = std::chrono::steady_clock::now() - start;
+
+ assert(result == false);
+ assert(elapsed > std::chrono::milliseconds(200));
+ assert(elapsed < std::chrono::milliseconds(600));
+ }
+
return 0;
}
diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for.pass.cpp
index eab38081d7b7f..e8bad68da6a9a 100644
--- a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for.pass.cpp
+++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for.pass.cpp
@@ -110,6 +110,21 @@ void test() {
t1.join();
}
+
+ // Regression test for LWG 3504: wait_for() with a floating-point
+ {
+ std::condition_variable_any cv;
+ std::mutex mutex;
+ std::unique_lock<std::mutex> lock(mutex);
+
+ auto start = std::chrono::steady_clock::now();
+ std::cv_status status = cv.wait_for(lock, std::chrono::duration<float>(0.25f));
+ auto elapsed = std::chrono::steady_clock::now() - start;
+
+ assert(status == std::cv_status::timeout);
+ assert(elapsed > std::chrono::milliseconds(200));
+ assert(elapsed < std::chrono::milliseconds(600));
+ }
}
int main(int, char**) {
diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_pred.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_pred.pass.cpp
index 67ba06b835297..3d8494fa12122 100644
--- a/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_pred.pass.cpp
+++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_pred.pass.cpp
@@ -160,6 +160,21 @@ void test() {
t2.join();
t1.join();
}
+
+ // Regression test for LWG 3504: wait_for() with a floating-point
+ {
+ std::condition_variable_any cv;
+ std::mutex mutex;
+ std::unique_lock<std::mutex> lock(mutex);
+
+ auto start = std::chrono::steady_clock::now();
+ bool result = cv.wait_for(lock, std::chrono::duration<float>(0.25f), [] { return false; });
+ auto elapsed = std::chrono::steady_clock::now() - start;
+
+ assert(result == false);
+ assert(elapsed > std::chrono::milliseconds(200));
+ assert(elapsed < std::chrono::milliseconds(600));
+ }
}
int main(int, char**) {
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
index 6e466b9f5a3df..3b3f7fd79855b 100644
--- 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
@@ -236,6 +236,23 @@ void test() {
}
}
#endif //!defined(TEST_HAS_NO_EXCEPTIONS)
+
+ // Regression test for LWG 3504: wait_for() with a floating-point
+ {
+ std::condition_variable_any cv;
+ std::mutex mutex;
+ std::unique_lock<std::mutex> lock(mutex);
+ std::stop_source ss;
+
+ auto start = std::chrono::steady_clock::now();
+ bool result = cv.wait_for(lock, ss.get_token(), std::chrono::duration<float>(0.25f),
+ [] { return false; });
+ auto elapsed = std::chrono::steady_clock::now() - start;
+
+ assert(result == false);
+ assert(elapsed > std::chrono::milliseconds(200));
+ assert(elapsed < std::chrono::milliseconds(600));
+ }
}
int main(int, char**) {
``````````
</details>
https://github.com/llvm/llvm-project/pull/222443
More information about the libcxx-commits
mailing list