From: Dan Albert Date: Mon, 16 Sep 2019 17:57:48 +0000 (+0000) Subject: Implement std::condition_variable via pthread_cond_clockwait() where available X-Git-Tag: llvmorg-11-init~9106 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=5e37d7f9ff257ec62d733d3d94b11f03e0fe51ca;p=platform%2Fupstream%2Fllvm.git Implement std::condition_variable via pthread_cond_clockwait() where available std::condition_variable is currently implemented via pthread_cond_timedwait() on systems that use pthread. This is problematic, since that function waits by default on CLOCK_REALTIME and libc++ does not provide any mechanism to change from this default. Due to this, regardless of if condition_variable::wait_until() is called with a chrono::system_clock or chrono::steady_clock parameter, condition_variable::wait_until() will wait using CLOCK_REALTIME. This is not accurate to the C++ standard as calling condition_variable::wait_until() with a chrono::steady_clock parameter should use CLOCK_MONOTONIC. This is particularly problematic because CLOCK_REALTIME is a bad choice as it is subject to discontinuous time adjustments, that may cause condition_variable::wait_until() to immediately timeout or wait indefinitely. This change fixes this issue with a new POSIX function, pthread_cond_clockwait() proposed on http://austingroupbugs.net/view.php?id=1216. The new function is similar to pthread_cond_timedwait() with the addition of a clock parameter that allows it to wait using either CLOCK_REALTIME or CLOCK_MONOTONIC, thus allowing condition_variable::wait_until() to wait using CLOCK_REALTIME for chrono::system_clock and CLOCK_MONOTONIC for chrono::steady_clock. pthread_cond_clockwait() is implemented in glibc (2.30 and later) and Android's bionic (Android API version 30 and later). This change additionally makes wait_for() and wait_until() with clocks other than chrono::system_clock use CLOCK_MONOTONIC. llvm-svn: 372016 --- diff --git a/libcxx/include/__config b/libcxx/include/__config index f631faa00345..bfbedf600f94 100644 --- a/libcxx/include/__config +++ b/libcxx/include/__config @@ -1087,6 +1087,12 @@ _LIBCPP_FUNC_VIS extern "C" void __sanitizer_annotate_contiguous_container( # endif // _LIBCPP_HAS_THREAD_API #endif // _LIBCPP_HAS_NO_THREADS +#if defined(_LIBCPP_HAS_THREAD_API_PTHREAD) +# if (defined(__ANDROID__) && __ANDROID_API__ >= 30) || _LIBCPP_GLIBC_PREREQ(2, 30) +# define _LIBCPP_HAS_COND_CLOCKWAIT +# endif +#endif + #if defined(_LIBCPP_HAS_NO_THREADS) && defined(_LIBCPP_HAS_THREAD_API_PTHREAD) #error _LIBCPP_HAS_THREAD_API_PTHREAD may only be defined when \ _LIBCPP_HAS_NO_THREADS is not defined. diff --git a/libcxx/include/__mutex_base b/libcxx/include/__mutex_base index a85ded8341e0..a4ac36161cba 100644 --- a/libcxx/include/__mutex_base +++ b/libcxx/include/__mutex_base @@ -15,6 +15,7 @@ #include #include <__threading_support> +#include #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) #pragma GCC system_header @@ -337,23 +338,75 @@ public: private: void __do_timed_wait(unique_lock& __lk, chrono::time_point) _NOEXCEPT; +#if defined(_LIBCPP_HAS_COND_CLOCKWAIT) + void __do_timed_wait(unique_lock& __lk, + chrono::time_point) _NOEXCEPT; +#endif + template + void __do_timed_wait(unique_lock& __lk, + chrono::time_point<_Clock, chrono::nanoseconds>) _NOEXCEPT; }; #endif // !_LIBCPP_HAS_NO_THREADS -template +template inline _LIBCPP_INLINE_VISIBILITY typename enable_if < - chrono::__is_duration<_To>::value, - _To + is_floating_point<_Rep>::value, + chrono::nanoseconds >::type -__ceil(chrono::duration<_Rep, _Period> __d) +__safe_nanosecond_cast(chrono::duration<_Rep, _Period> __d) { using namespace chrono; - _To __r = duration_cast<_To>(__d); - if (__r < __d) - ++__r; - return __r; + using __ratio = ratio_divide<_Period, nano>; + using __ns_rep = nanoseconds::rep; + _Rep __result_float = __d.count() * __ratio::num / __ratio::den; + + _Rep __result_max = numeric_limits<__ns_rep>::max(); + if (__result_float >= __result_max) { + return nanoseconds::max(); + } + + _Rep __result_min = numeric_limits<__ns_rep>::min(); + if (__result_float <= __result_min) { + return nanoseconds::min(); + } + + return nanoseconds(static_cast<__ns_rep>(__result_float)); +} + +template +inline _LIBCPP_INLINE_VISIBILITY +typename enable_if +< + !is_floating_point<_Rep>::value, + chrono::nanoseconds +>::type +__safe_nanosecond_cast(chrono::duration<_Rep, _Period> __d) +{ + using namespace chrono; + if (__d.count() == 0) { + return nanoseconds(0); + } + + using __ratio = ratio_divide<_Period, nano>; + using __ns_rep = nanoseconds::rep; + __ns_rep __result_max = std::numeric_limits<__ns_rep>::max(); + if (__d.count() > 0 && __d.count() > __result_max / __ratio::num) { + return nanoseconds::max(); + } + + __ns_rep __result_min = std::numeric_limits<__ns_rep>::min(); + if (__d.count() < 0 && __d.count() < __result_min / __ratio::num) { + return nanoseconds::min(); + } + + __ns_rep __result = __d.count() * __ratio::num / __ratio::den; + if (__result == 0) { + return nanoseconds(1); + } + + return nanoseconds(__result); } #ifndef _LIBCPP_HAS_NO_THREADS @@ -371,7 +424,15 @@ condition_variable::wait_until(unique_lock& __lk, const chrono::time_point<_Clock, _Duration>& __t) { using namespace chrono; - wait_for(__lk, __t - _Clock::now()); + using __clock_tp_ns = time_point<_Clock, nanoseconds>; + + typename _Clock::time_point __now = _Clock::now(); + if (__t <= __now) + return cv_status::timeout; + + __clock_tp_ns __t_ns = __clock_tp_ns(__safe_nanosecond_cast(__t.time_since_epoch())); + + __do_timed_wait(__lk, __t_ns); return _Clock::now() < __t ? cv_status::no_timeout : cv_status::timeout; } @@ -397,15 +458,25 @@ condition_variable::wait_for(unique_lock& __lk, using namespace chrono; if (__d <= __d.zero()) return cv_status::timeout; - typedef time_point > __sys_tpf; - typedef time_point __sys_tpi; - __sys_tpf _Max = __sys_tpi::max(); + using __ns_rep = nanoseconds::rep; steady_clock::time_point __c_now = steady_clock::now(); - system_clock::time_point __s_now = system_clock::now(); - if (_Max - __d > __s_now) - __do_timed_wait(__lk, __s_now + __ceil(__d)); - else - __do_timed_wait(__lk, __sys_tpi::max()); + +#if defined(_LIBCPP_HAS_COND_CLOCKWAIT) + using __clock_tp_ns = time_point; + __ns_rep __now_count_ns = __safe_nanosecond_cast(__c_now.time_since_epoch()).count(); +#else + using __clock_tp_ns = time_point; + __ns_rep __now_count_ns = __safe_nanosecond_cast(system_clock::now().time_since_epoch()).count(); +#endif + + __ns_rep __d_ns_count = __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; } @@ -421,6 +492,46 @@ condition_variable::wait_for(unique_lock& __lk, _VSTD::move(__pred)); } +#if defined(_LIBCPP_HAS_COND_CLOCKWAIT) +inline +void +condition_variable::__do_timed_wait(unique_lock& __lk, + chrono::time_point __tp) _NOEXCEPT +{ + using namespace chrono; + if (!__lk.owns_lock()) + __throw_system_error(EPERM, + "condition_variable::timed wait: mutex not locked"); + nanoseconds __d = __tp.time_since_epoch(); + timespec __ts; + seconds __s = duration_cast(__d); + using __ts_sec = decltype(__ts.tv_sec); + const __ts_sec __ts_sec_max = numeric_limits<__ts_sec>::max(); + if (__s.count() < __ts_sec_max) + { + __ts.tv_sec = static_cast<__ts_sec>(__s.count()); + __ts.tv_nsec = (__d - __s).count(); + } + else + { + __ts.tv_sec = __ts_sec_max; + __ts.tv_nsec = giga::num - 1; + } + int __ec = pthread_cond_clockwait(&__cv_, __lk.mutex()->native_handle(), CLOCK_MONOTONIC, &__ts); + if (__ec != 0 && __ec != ETIMEDOUT) + __throw_system_error(__ec, "condition_variable timed_wait failed"); +} +#endif // _LIBCPP_HAS_COND_CLOCKWAIT + +template +inline +void +condition_variable::__do_timed_wait(unique_lock& __lk, + chrono::time_point<_Clock, chrono::nanoseconds> __tp) _NOEXCEPT +{ + wait_for(__lk, __tp - _Clock::now()); +} + #endif // !_LIBCPP_HAS_NO_THREADS _LIBCPP_END_NAMESPACE_STD diff --git a/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_until.pass.cpp b/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_until.pass.cpp index 12ccf3f1c06e..e5c77f28eb84 100644 --- a/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_until.pass.cpp +++ b/libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_until.pass.cpp @@ -25,12 +25,12 @@ #include "test_macros.h" -struct Clock +struct TestClock { typedef std::chrono::milliseconds duration; typedef duration::rep rep; typedef duration::period period; - typedef std::chrono::time_point time_point; + typedef std::chrono::time_point time_point; static const bool is_steady = true; static time_point now() @@ -50,35 +50,40 @@ int test2 = 0; int runs = 0; +template void f() { std::unique_lock lk(mut); assert(test2 == 0); test1 = 1; cv.notify_one(); - Clock::time_point t0 = Clock::now(); - Clock::time_point t = t0 + Clock::duration(250); + typename Clock::time_point t0 = Clock::now(); + typename Clock::time_point t = t0 + std::chrono::milliseconds(250); while (test2 == 0 && cv.wait_until(lk, t) == std::cv_status::no_timeout) ; - Clock::time_point t1 = Clock::now(); + typename Clock::time_point t1 = Clock::now(); if (runs == 0) { - assert(t1 - t0 < Clock::duration(250)); + assert(t1 - t0 < std::chrono::milliseconds(250)); assert(test2 != 0); } else { - assert(t1 - t0 - Clock::duration(250) < Clock::duration(50)); + assert(t1 - t0 - std::chrono::milliseconds(250) < std::chrono::milliseconds(50)); assert(test2 == 0); } ++runs; } -int main(int, char**) +template +void run_test() { + runs = 0; + test1 = 0; + test2 = 0; { std::unique_locklk(mut); - std::thread t(f); + std::thread t(f); assert(test1 == 0); while (test1 == 0) cv.wait(lk); @@ -92,7 +97,7 @@ int main(int, char**) test2 = 0; { std::unique_locklk(mut); - std::thread t(f); + std::thread t(f); assert(test1 == 0); while (test1 == 0) cv.wait(lk); @@ -100,6 +105,12 @@ int main(int, char**) lk.unlock(); t.join(); } +} - return 0; +int main(int, char**) +{ + run_test(); + run_test(); + run_test(); + return 0; }