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

via libcxx-commits libcxx-commits at lists.llvm.org
Sun Oct 11 02:45:01 PDT 2026


Author: Vishank Singh
Date: 2026-10-11T17:44:56+08:00
New Revision: 964ed3b480ea4d03884ef10c3681854d0a5ad194

URL: https://github.com/llvm/llvm-project/commit/964ed3b480ea4d03884ef10c3681854d0a5ad194
DIFF: https://github.com/llvm/llvm-project/commit/964ed3b480ea4d03884ef10c3681854d0a5ad194.diff

LOG: [libc++][chrono][threading] Implement LWG 3504: `condition_variable::wait_for` is overspecified (#222443)

Implement the relative-to-absolute conversion mandated by LWG 3504 using
`ceil<steady_clock::duration>` to avoid precision loss with
floating-point
durations.

- Add internal `chrono::__ceil` (usable in all dialects) and make
`chrono::ceil`
  forward to it.
- Introduce `__rel_to_abs` helper and update all `wait_for` overloads on
  `condition_variable` / `condition_variable_any`.
- Move the previous nanosecond conversion into `__do_timed_wait` to 
  avoid recursion after the change.
- Add regression tests for floating-point durations.

Fixes #189807

Added: 
    

Modified: 
    libcxx/docs/Status/Cxx26Issues.csv
    libcxx/include/__chrono/duration.h
    libcxx/include/__condition_variable/condition_variable.h
    libcxx/include/condition_variable
    libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for.pass.cpp
    libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_for_pred.pass.cpp
    libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for.pass.cpp
    libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_pred.pass.cpp
    libcxx/test/std/thread/thread.condition/thread.condition.condvarany/wait_for_token_pred.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/Cxx26Issues.csv b/libcxx/docs/Status/Cxx26Issues.csv
index b46b90167298e..180d4a4870e7b 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/__chrono/duration.h b/libcxx/include/__chrono/duration.h
index 7b6c11adae86b..ecd3b1f97cc6f 100644
--- a/libcxx/include/__chrono/duration.h
+++ b/libcxx/include/__chrono/duration.h
@@ -130,6 +130,14 @@ struct duration_values {
   }
 };
 
+template <class _ToDuration, class _Rep, class _Period>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _ToDuration __ceil(const duration<_Rep, _Period>& __d) {
+  _ToDuration __t = chrono::duration_cast<_ToDuration>(__d);
+  if (__t < __d)
+    __t = __t + _ToDuration(1);
+  return __t;
+}
+
 #if _LIBCPP_STD_VER >= 17
 template <class _ToDuration, class _Rep, class _Period, enable_if_t<__is_duration_v<_ToDuration>, int> = 0>
 [[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _ToDuration floor(const duration<_Rep, _Period>& __d) {
@@ -141,10 +149,7 @@ template <class _ToDuration, class _Rep, class _Period, enable_if_t<__is_duratio
 
 template <class _ToDuration, class _Rep, class _Period, enable_if_t<__is_duration_v<_ToDuration>, int> = 0>
 [[__nodiscard__]] inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR _ToDuration ceil(const duration<_Rep, _Period>& __d) {
-  _ToDuration __t = chrono::duration_cast<_ToDuration>(__d);
-  if (__t < __d)
-    __t = __t + _ToDuration{1};
-  return __t;
+  return chrono::__ceil<_ToDuration>(__d);
 }
 
 template <class _ToDuration, class _Rep, class _Period, enable_if_t<__is_duration_v<_ToDuration>, int> = 0>

diff  --git a/libcxx/include/__condition_variable/condition_variable.h b/libcxx/include/__condition_variable/condition_variable.h
index f95de5391918f..0787eaf2a371b 100644
--- a/libcxx/include/__condition_variable/condition_variable.h
+++ b/libcxx/include/__condition_variable/condition_variable.h
@@ -95,6 +95,11 @@ inline _LIBCPP_HIDE_FROM_ABI chrono::nanoseconds __safe_nanosecond_cast(chrono::
   return nanoseconds(__result);
 }
 
+template <class _Duration>
+_LIBCPP_HIDE_FROM_ABI chrono::steady_clock::time_point __rel_to_abs(const _Duration& __rel_time) {
+  return chrono::steady_clock::now() + chrono::__ceil<chrono::steady_clock::duration>(__rel_time);
+}
+
 class _LIBCPP_EXPORTED_FROM_ABI _LIBCPP_WARN_UNUSED condition_variable {
   __libcpp_condvar_t __cv_ = _LIBCPP_CONDVAR_INITIALIZER;
 
@@ -149,29 +154,7 @@ class _LIBCPP_EXPORTED_FROM_ABI _LIBCPP_WARN_UNUSED 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>
@@ -199,7 +182,7 @@ class _LIBCPP_EXPORTED_FROM_ABI _LIBCPP_WARN_UNUSED 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
@@ -229,7 +212,28 @@ inline void condition_variable::__do_timed_wait(
 template <class _Clock>
 inline void condition_variable::__do_timed_wait(unique_lock<mutex>& __lk,
                                                 chrono::time_point<_Clock, chrono::nanoseconds> __tp) _NOEXCEPT {
-  wait_for(__lk, __tp - _Clock::now());
+  using namespace chrono;
+  nanoseconds __d = __tp - _Clock::now();
+  if (__d <= __d.zero())
+    return;
+  using __ns_rep = nanoseconds::rep;
+
+#  if _LIBCPP_HAS_COND_CLOCKWAIT
+  steady_clock::time_point __c_now = steady_clock::now();
+  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)));
+  }
 }
 
 _LIBCPP_END_EXPLICIT_ABI_ANNOTATIONS

diff  --git a/libcxx/include/condition_variable b/libcxx/include/condition_variable
index 39d7a687b4181..2e32bcdec0195 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..d0885cb919810 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,29 @@ 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);
+
+    using float_sec = std::chrono::duration<float>;
+    const float_sec total{0.25f};
+
+    auto start            = std::chrono::steady_clock::now();
+    std::cv_status status = std::cv_status::no_timeout;
+
+    while (status == std::cv_status::no_timeout) {
+      auto left = total - std::chrono::duration_cast<float_sec>(std::chrono::steady_clock::now() - start);
+      if (left <= float_sec::zero())
+        break;
+      status = cv.wait_for(lock, left);
+    }
+
+    auto elapsed = std::chrono::steady_clock::now() - start;
+    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..bcf22a270a453 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..37d0c8dd55c5c 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,30 @@ 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);
+
+    using float_sec = std::chrono::duration<float>;
+    const float_sec total{0.25f};
+
+    auto start            = std::chrono::steady_clock::now();
+    std::cv_status status = std::cv_status::no_timeout;
+
+    while (status == std::cv_status::no_timeout) {
+      auto left = total - std::chrono::duration_cast<float_sec>(std::chrono::steady_clock::now() - start);
+      if (left <= float_sec::zero())
+        break;
+      status = cv.wait_for(lock, left);
+    }
+
+    auto elapsed = std::chrono::steady_clock::now() - start;
+    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..fb9b3ccdd2d02 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..7b991d285e76e 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,22 @@ 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