[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