[libcxx-commits] [libcxx] [libc++][chrono][threading] Implement LWG 3504: `condition_variable::wait_for` is overspecified (PR #222443)

Vishank Singh via libcxx-commits libcxx-commits at lists.llvm.org
Wed Sep 9 13:52:15 PDT 2026


https://github.com/VishankSingh created https://github.com/llvm/llvm-project/pull/222443

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

>From 0ddaed88011c9d7e52658f8d78cea62d46b0e5d3 Mon Sep 17 00:00:00 2001
From: vishanksingh <vishanksinghh at gmail.com>
Date: Thu, 10 Sep 2026 02:11:00 +0530
Subject: [PATCH] [libc++][chrono][threading] Implement LWG 3504:
 condition_variable::wait_for is overspecified

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
---
 libcxx/docs/Status/Cxx26Issues.csv            |  2 +-
 .../__condition_variable/condition_variable.h | 32 +++++--------------
 libcxx/include/condition_variable             |  6 ++--
 .../wait_for.pass.cpp                         | 15 +++++++++
 .../wait_for_pred.pass.cpp                    | 15 +++++++++
 .../wait_for.pass.cpp                         | 15 +++++++++
 .../wait_for_pred.pass.cpp                    | 15 +++++++++
 .../wait_for_token_pred.pass.cpp              | 17 ++++++++++
 8 files changed, 89 insertions(+), 28 deletions(-)

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**) {



More information about the libcxx-commits mailing list