Skip to content

Commit 5e37d7f

Browse files
committed
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.<Paste> llvm-svn: 372016
1 parent 6fcd4e0 commit 5e37d7f

File tree

3 files changed

+156
-28
lines changed

3 files changed

+156
-28
lines changed

libcxx/include/__config

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,6 +1087,12 @@ _LIBCPP_FUNC_VIS extern "C" void __sanitizer_annotate_contiguous_container(
10871087
# endif // _LIBCPP_HAS_THREAD_API
10881088
#endif // _LIBCPP_HAS_NO_THREADS
10891089

1090+
#if defined(_LIBCPP_HAS_THREAD_API_PTHREAD)
1091+
# if (defined(__ANDROID__) && __ANDROID_API__ >= 30) || _LIBCPP_GLIBC_PREREQ(2, 30)
1092+
# define _LIBCPP_HAS_COND_CLOCKWAIT
1093+
# endif
1094+
#endif
1095+
10901096
#if defined(_LIBCPP_HAS_NO_THREADS) && defined(_LIBCPP_HAS_THREAD_API_PTHREAD)
10911097
#error _LIBCPP_HAS_THREAD_API_PTHREAD may only be defined when \
10921098
_LIBCPP_HAS_NO_THREADS is not defined.

libcxx/include/__mutex_base

Lines changed: 128 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <system_error>
1616
#include <__threading_support>
1717

18+
#include <time.h>
1819

1920
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
2021
#pragma GCC system_header
@@ -337,23 +338,75 @@ public:
337338
private:
338339
void __do_timed_wait(unique_lock<mutex>& __lk,
339340
chrono::time_point<chrono::system_clock, chrono::nanoseconds>) _NOEXCEPT;
341+
#if defined(_LIBCPP_HAS_COND_CLOCKWAIT)
342+
void __do_timed_wait(unique_lock<mutex>& __lk,
343+
chrono::time_point<chrono::steady_clock, chrono::nanoseconds>) _NOEXCEPT;
344+
#endif
345+
template <class _Clock>
346+
void __do_timed_wait(unique_lock<mutex>& __lk,
347+
chrono::time_point<_Clock, chrono::nanoseconds>) _NOEXCEPT;
340348
};
341349
#endif // !_LIBCPP_HAS_NO_THREADS
342350

343-
template <class _To, class _Rep, class _Period>
351+
template <class _Rep, class _Period>
344352
inline _LIBCPP_INLINE_VISIBILITY
345353
typename enable_if
346354
<
347-
chrono::__is_duration<_To>::value,
348-
_To
355+
is_floating_point<_Rep>::value,
356+
chrono::nanoseconds
349357
>::type
350-
__ceil(chrono::duration<_Rep, _Period> __d)
358+
__safe_nanosecond_cast(chrono::duration<_Rep, _Period> __d)
351359
{
352360
using namespace chrono;
353-
_To __r = duration_cast<_To>(__d);
354-
if (__r < __d)
355-
++__r;
356-
return __r;
361+
using __ratio = ratio_divide<_Period, nano>;
362+
using __ns_rep = nanoseconds::rep;
363+
_Rep __result_float = __d.count() * __ratio::num / __ratio::den;
364+
365+
_Rep __result_max = numeric_limits<__ns_rep>::max();
366+
if (__result_float >= __result_max) {
367+
return nanoseconds::max();
368+
}
369+
370+
_Rep __result_min = numeric_limits<__ns_rep>::min();
371+
if (__result_float <= __result_min) {
372+
return nanoseconds::min();
373+
}
374+
375+
return nanoseconds(static_cast<__ns_rep>(__result_float));
376+
}
377+
378+
template <class _Rep, class _Period>
379+
inline _LIBCPP_INLINE_VISIBILITY
380+
typename enable_if
381+
<
382+
!is_floating_point<_Rep>::value,
383+
chrono::nanoseconds
384+
>::type
385+
__safe_nanosecond_cast(chrono::duration<_Rep, _Period> __d)
386+
{
387+
using namespace chrono;
388+
if (__d.count() == 0) {
389+
return nanoseconds(0);
390+
}
391+
392+
using __ratio = ratio_divide<_Period, nano>;
393+
using __ns_rep = nanoseconds::rep;
394+
__ns_rep __result_max = std::numeric_limits<__ns_rep>::max();
395+
if (__d.count() > 0 && __d.count() > __result_max / __ratio::num) {
396+
return nanoseconds::max();
397+
}
398+
399+
__ns_rep __result_min = std::numeric_limits<__ns_rep>::min();
400+
if (__d.count() < 0 && __d.count() < __result_min / __ratio::num) {
401+
return nanoseconds::min();
402+
}
403+
404+
__ns_rep __result = __d.count() * __ratio::num / __ratio::den;
405+
if (__result == 0) {
406+
return nanoseconds(1);
407+
}
408+
409+
return nanoseconds(__result);
357410
}
358411

359412
#ifndef _LIBCPP_HAS_NO_THREADS
@@ -371,7 +424,15 @@ condition_variable::wait_until(unique_lock<mutex>& __lk,
371424
const chrono::time_point<_Clock, _Duration>& __t)
372425
{
373426
using namespace chrono;
374-
wait_for(__lk, __t - _Clock::now());
427+
using __clock_tp_ns = time_point<_Clock, nanoseconds>;
428+
429+
typename _Clock::time_point __now = _Clock::now();
430+
if (__t <= __now)
431+
return cv_status::timeout;
432+
433+
__clock_tp_ns __t_ns = __clock_tp_ns(__safe_nanosecond_cast(__t.time_since_epoch()));
434+
435+
__do_timed_wait(__lk, __t_ns);
375436
return _Clock::now() < __t ? cv_status::no_timeout : cv_status::timeout;
376437
}
377438

@@ -397,15 +458,25 @@ condition_variable::wait_for(unique_lock<mutex>& __lk,
397458
using namespace chrono;
398459
if (__d <= __d.zero())
399460
return cv_status::timeout;
400-
typedef time_point<system_clock, duration<long double, nano> > __sys_tpf;
401-
typedef time_point<system_clock, nanoseconds> __sys_tpi;
402-
__sys_tpf _Max = __sys_tpi::max();
461+
using __ns_rep = nanoseconds::rep;
403462
steady_clock::time_point __c_now = steady_clock::now();
404-
system_clock::time_point __s_now = system_clock::now();
405-
if (_Max - __d > __s_now)
406-
__do_timed_wait(__lk, __s_now + __ceil<nanoseconds>(__d));
407-
else
408-
__do_timed_wait(__lk, __sys_tpi::max());
463+
464+
#if defined(_LIBCPP_HAS_COND_CLOCKWAIT)
465+
using __clock_tp_ns = time_point<steady_clock, nanoseconds>;
466+
__ns_rep __now_count_ns = __safe_nanosecond_cast(__c_now.time_since_epoch()).count();
467+
#else
468+
using __clock_tp_ns = time_point<system_clock, nanoseconds>;
469+
__ns_rep __now_count_ns = __safe_nanosecond_cast(system_clock::now().time_since_epoch()).count();
470+
#endif
471+
472+
__ns_rep __d_ns_count = __safe_nanosecond_cast(__d).count();
473+
474+
if (__now_count_ns > numeric_limits<__ns_rep>::max() - __d_ns_count) {
475+
__do_timed_wait(__lk, __clock_tp_ns::max());
476+
} else {
477+
__do_timed_wait(__lk, __clock_tp_ns(nanoseconds(__now_count_ns + __d_ns_count)));
478+
}
479+
409480
return steady_clock::now() - __c_now < __d ? cv_status::no_timeout :
410481
cv_status::timeout;
411482
}
@@ -421,6 +492,46 @@ condition_variable::wait_for(unique_lock<mutex>& __lk,
421492
_VSTD::move(__pred));
422493
}
423494

495+
#if defined(_LIBCPP_HAS_COND_CLOCKWAIT)
496+
inline
497+
void
498+
condition_variable::__do_timed_wait(unique_lock<mutex>& __lk,
499+
chrono::time_point<chrono::steady_clock, chrono::nanoseconds> __tp) _NOEXCEPT
500+
{
501+
using namespace chrono;
502+
if (!__lk.owns_lock())
503+
__throw_system_error(EPERM,
504+
"condition_variable::timed wait: mutex not locked");
505+
nanoseconds __d = __tp.time_since_epoch();
506+
timespec __ts;
507+
seconds __s = duration_cast<seconds>(__d);
508+
using __ts_sec = decltype(__ts.tv_sec);
509+
const __ts_sec __ts_sec_max = numeric_limits<__ts_sec>::max();
510+
if (__s.count() < __ts_sec_max)
511+
{
512+
__ts.tv_sec = static_cast<__ts_sec>(__s.count());
513+
__ts.tv_nsec = (__d - __s).count();
514+
}
515+
else
516+
{
517+
__ts.tv_sec = __ts_sec_max;
518+
__ts.tv_nsec = giga::num - 1;
519+
}
520+
int __ec = pthread_cond_clockwait(&__cv_, __lk.mutex()->native_handle(), CLOCK_MONOTONIC, &__ts);
521+
if (__ec != 0 && __ec != ETIMEDOUT)
522+
__throw_system_error(__ec, "condition_variable timed_wait failed");
523+
}
524+
#endif // _LIBCPP_HAS_COND_CLOCKWAIT
525+
526+
template <class _Clock>
527+
inline
528+
void
529+
condition_variable::__do_timed_wait(unique_lock<mutex>& __lk,
530+
chrono::time_point<_Clock, chrono::nanoseconds> __tp) _NOEXCEPT
531+
{
532+
wait_for(__lk, __tp - _Clock::now());
533+
}
534+
424535
#endif // !_LIBCPP_HAS_NO_THREADS
425536

426537
_LIBCPP_END_NAMESPACE_STD

libcxx/test/std/thread/thread.condition/thread.condition.condvar/wait_until.pass.cpp

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525

2626
#include "test_macros.h"
2727

28-
struct Clock
28+
struct TestClock
2929
{
3030
typedef std::chrono::milliseconds duration;
3131
typedef duration::rep rep;
3232
typedef duration::period period;
33-
typedef std::chrono::time_point<Clock> time_point;
33+
typedef std::chrono::time_point<TestClock> time_point;
3434
static const bool is_steady = true;
3535

3636
static time_point now()
@@ -50,35 +50,40 @@ int test2 = 0;
5050

5151
int runs = 0;
5252

53+
template <typename Clock>
5354
void f()
5455
{
5556
std::unique_lock<std::mutex> lk(mut);
5657
assert(test2 == 0);
5758
test1 = 1;
5859
cv.notify_one();
59-
Clock::time_point t0 = Clock::now();
60-
Clock::time_point t = t0 + Clock::duration(250);
60+
typename Clock::time_point t0 = Clock::now();
61+
typename Clock::time_point t = t0 + std::chrono::milliseconds(250);
6162
while (test2 == 0 && cv.wait_until(lk, t) == std::cv_status::no_timeout)
6263
;
63-
Clock::time_point t1 = Clock::now();
64+
typename Clock::time_point t1 = Clock::now();
6465
if (runs == 0)
6566
{
66-
assert(t1 - t0 < Clock::duration(250));
67+
assert(t1 - t0 < std::chrono::milliseconds(250));
6768
assert(test2 != 0);
6869
}
6970
else
7071
{
71-
assert(t1 - t0 - Clock::duration(250) < Clock::duration(50));
72+
assert(t1 - t0 - std::chrono::milliseconds(250) < std::chrono::milliseconds(50));
7273
assert(test2 == 0);
7374
}
7475
++runs;
7576
}
7677

77-
int main(int, char**)
78+
template <typename Clock>
79+
void run_test()
7880
{
81+
runs = 0;
82+
test1 = 0;
83+
test2 = 0;
7984
{
8085
std::unique_lock<std::mutex>lk(mut);
81-
std::thread t(f);
86+
std::thread t(f<Clock>);
8287
assert(test1 == 0);
8388
while (test1 == 0)
8489
cv.wait(lk);
@@ -92,14 +97,20 @@ int main(int, char**)
9297
test2 = 0;
9398
{
9499
std::unique_lock<std::mutex>lk(mut);
95-
std::thread t(f);
100+
std::thread t(f<Clock>);
96101
assert(test1 == 0);
97102
while (test1 == 0)
98103
cv.wait(lk);
99104
assert(test1 != 0);
100105
lk.unlock();
101106
t.join();
102107
}
108+
}
103109

104-
return 0;
110+
int main(int, char**)
111+
{
112+
run_test<TestClock>();
113+
run_test<std::chrono::steady_clock>();
114+
run_test<std::chrono::system_clock>();
115+
return 0;
105116
}

0 commit comments

Comments
 (0)