ConditionVariable_posix.cpp (5156B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/Assertions.h" 8 #include "mozilla/CheckedInt.h" 9 10 #include <errno.h> 11 #include <pthread.h> 12 #include <time.h> 13 14 #include "mozilla/PlatformConditionVariable.h" 15 #include "mozilla/PlatformMutex.h" 16 #include "MutexPlatformData_posix.h" 17 18 using mozilla::CheckedInt; 19 using mozilla::TimeDuration; 20 21 static const long NanoSecPerSec = 1000000000; 22 23 // macOS has the clock functions, but not pthread_condattr_setclock. 24 #if defined(HAVE_CLOCK_MONOTONIC) && !defined(__APPLE__) 25 # define CV_USE_CLOCK_API 26 #endif 27 28 #ifdef CV_USE_CLOCK_API 29 // The C++ specification defines std::condition_variable::wait_for in terms of 30 // std::chrono::steady_clock, which is closest to CLOCK_MONOTONIC. 31 static const clockid_t WhichClock = CLOCK_MONOTONIC; 32 33 // While timevaladd is widely available to work with timevals, the newer 34 // timespec structure is largely lacking such conveniences. Thankfully, the 35 // utilities available in MFBT make implementing our own quite easy. 36 static void moz_timespecadd(struct timespec* lhs, struct timespec* rhs, 37 struct timespec* result) { 38 // Add nanoseconds. This may wrap, but not above 2 billion. 39 MOZ_RELEASE_ASSERT(lhs->tv_nsec < NanoSecPerSec); 40 MOZ_RELEASE_ASSERT(rhs->tv_nsec < NanoSecPerSec); 41 result->tv_nsec = lhs->tv_nsec + rhs->tv_nsec; 42 43 // Add seconds, checking for overflow in the platform specific time_t type. 44 CheckedInt<time_t> sec = CheckedInt<time_t>(lhs->tv_sec) + rhs->tv_sec; 45 46 // If nanoseconds overflowed, carry the result over into seconds. 47 if (result->tv_nsec >= NanoSecPerSec) { 48 MOZ_RELEASE_ASSERT(result->tv_nsec < 2 * NanoSecPerSec); 49 result->tv_nsec -= NanoSecPerSec; 50 sec += 1; 51 } 52 53 // Extracting the value asserts that there was no overflow. 54 MOZ_RELEASE_ASSERT(sec.isValid()); 55 result->tv_sec = sec.value(); 56 } 57 #endif 58 59 struct mozilla::detail::ConditionVariableImpl::PlatformData { 60 pthread_cond_t ptCond; 61 }; 62 63 mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() { 64 pthread_cond_t* ptCond = &platformData()->ptCond; 65 66 #ifdef CV_USE_CLOCK_API 67 pthread_condattr_t attr; 68 int r0 = pthread_condattr_init(&attr); 69 MOZ_RELEASE_ASSERT(!r0); 70 71 int r1 = pthread_condattr_setclock(&attr, WhichClock); 72 MOZ_RELEASE_ASSERT(!r1); 73 74 int r2 = pthread_cond_init(ptCond, &attr); 75 MOZ_RELEASE_ASSERT(!r2); 76 77 int r3 = pthread_condattr_destroy(&attr); 78 MOZ_RELEASE_ASSERT(!r3); 79 #else 80 int r = pthread_cond_init(ptCond, NULL); 81 MOZ_RELEASE_ASSERT(!r); 82 #endif 83 } 84 85 mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() { 86 int r = pthread_cond_destroy(&platformData()->ptCond); 87 MOZ_RELEASE_ASSERT(r == 0); 88 } 89 90 void mozilla::detail::ConditionVariableImpl::notify_one() { 91 int r = pthread_cond_signal(&platformData()->ptCond); 92 MOZ_RELEASE_ASSERT(r == 0); 93 } 94 95 void mozilla::detail::ConditionVariableImpl::notify_all() { 96 int r = pthread_cond_broadcast(&platformData()->ptCond); 97 MOZ_RELEASE_ASSERT(r == 0); 98 } 99 100 void mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) { 101 pthread_cond_t* ptCond = &platformData()->ptCond; 102 pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex; 103 104 int r = pthread_cond_wait(ptCond, ptMutex); 105 MOZ_RELEASE_ASSERT(r == 0); 106 } 107 108 mozilla::CVStatus mozilla::detail::ConditionVariableImpl::wait_for( 109 MutexImpl& lock, const TimeDuration& a_rel_time) { 110 if (a_rel_time == TimeDuration::Forever()) { 111 wait(lock); 112 return CVStatus::NoTimeout; 113 } 114 115 pthread_cond_t* ptCond = &platformData()->ptCond; 116 pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex; 117 int r; 118 119 // Clamp to 0, as time_t is unsigned. 120 TimeDuration rel_time = a_rel_time < TimeDuration::FromSeconds(0) 121 ? TimeDuration::FromSeconds(0) 122 : a_rel_time; 123 124 // Convert the duration to a timespec. 125 struct timespec rel_ts; 126 rel_ts.tv_sec = static_cast<time_t>(rel_time.ToSeconds()); 127 rel_ts.tv_nsec = 128 static_cast<uint64_t>(rel_time.ToMicroseconds() * 1000.0) % NanoSecPerSec; 129 130 #ifdef CV_USE_CLOCK_API 131 struct timespec now_ts; 132 r = clock_gettime(WhichClock, &now_ts); 133 MOZ_RELEASE_ASSERT(!r); 134 135 struct timespec abs_ts; 136 moz_timespecadd(&now_ts, &rel_ts, &abs_ts); 137 138 r = pthread_cond_timedwait(ptCond, ptMutex, &abs_ts); 139 #else 140 // Our non-clock-supporting platforms, OS X and Android, do support waiting 141 // on a condition variable with a relative timeout. 142 r = pthread_cond_timedwait_relative_np(ptCond, ptMutex, &rel_ts); 143 #endif 144 145 if (r == 0) { 146 return CVStatus::NoTimeout; 147 } 148 MOZ_RELEASE_ASSERT(r == ETIMEDOUT); 149 return CVStatus::Timeout; 150 } 151 152 mozilla::detail::ConditionVariableImpl::PlatformData* 153 mozilla::detail::ConditionVariableImpl::platformData() { 154 static_assert(sizeof platformData_ >= sizeof(PlatformData), 155 "platformData_ is too small"); 156 return reinterpret_cast<PlatformData*>(platformData_); 157 }