/src/mozilla-central/mozglue/misc/ConditionVariable_posix.cpp
Line | Count | Source (jump to first uncovered line) |
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 <stdlib.h> |
13 | | #include <time.h> |
14 | | #include <unistd.h> |
15 | | |
16 | | #include "mozilla/PlatformConditionVariable.h" |
17 | | #include "mozilla/PlatformMutex.h" |
18 | | #include "MutexPlatformData_posix.h" |
19 | | |
20 | | using mozilla::CheckedInt; |
21 | | using mozilla::TimeDuration; |
22 | | using mozilla::TimeStamp; |
23 | | |
24 | | static const long NanoSecPerSec = 1000000000; |
25 | | |
26 | | // Android 32-bit & macOS 10.12 has the clock functions, but not pthread_condattr_setclock. |
27 | | #if defined(HAVE_CLOCK_MONOTONIC) && \ |
28 | | !(defined(__ANDROID__) && !defined(__LP64__)) && !defined(__APPLE__) |
29 | | # define CV_USE_CLOCK_API |
30 | | #endif |
31 | | |
32 | | #ifdef CV_USE_CLOCK_API |
33 | | // The C++ specification defines std::condition_variable::wait_for in terms of |
34 | | // std::chrono::steady_clock, which is closest to CLOCK_MONOTONIC. |
35 | | static const clockid_t WhichClock = CLOCK_MONOTONIC; |
36 | | |
37 | | // While timevaladd is widely available to work with timevals, the newer |
38 | | // timespec structure is largely lacking such conveniences. Thankfully, the |
39 | | // utilities available in MFBT make implementing our own quite easy. |
40 | | static void |
41 | | moz_timespecadd(struct timespec* lhs, struct timespec* rhs, struct timespec* result) |
42 | 98 | { |
43 | 98 | // Add nanoseconds. This may wrap, but not above 2 billion. |
44 | 98 | MOZ_RELEASE_ASSERT(lhs->tv_nsec < NanoSecPerSec); |
45 | 98 | MOZ_RELEASE_ASSERT(rhs->tv_nsec < NanoSecPerSec); |
46 | 98 | result->tv_nsec = lhs->tv_nsec + rhs->tv_nsec; |
47 | 98 | |
48 | 98 | // Add seconds, checking for overflow in the platform specific time_t type. |
49 | 98 | CheckedInt<time_t> sec = CheckedInt<time_t>(lhs->tv_sec) + rhs->tv_sec; |
50 | 98 | |
51 | 98 | // If nanoseconds overflowed, carry the result over into seconds. |
52 | 98 | if (result->tv_nsec >= NanoSecPerSec) { |
53 | 12 | MOZ_RELEASE_ASSERT(result->tv_nsec < 2 * NanoSecPerSec); |
54 | 12 | result->tv_nsec -= NanoSecPerSec; |
55 | 12 | sec += 1; |
56 | 12 | } |
57 | 98 | |
58 | 98 | // Extracting the value asserts that there was no overflow. |
59 | 98 | MOZ_RELEASE_ASSERT(sec.isValid()); |
60 | 98 | result->tv_sec = sec.value(); |
61 | 98 | } |
62 | | #endif |
63 | | |
64 | | struct mozilla::detail::ConditionVariableImpl::PlatformData |
65 | | { |
66 | | pthread_cond_t ptCond; |
67 | | }; |
68 | | |
69 | | mozilla::detail::ConditionVariableImpl::ConditionVariableImpl() |
70 | 96 | { |
71 | 96 | pthread_cond_t* ptCond = &platformData()->ptCond; |
72 | 96 | |
73 | 96 | #ifdef CV_USE_CLOCK_API |
74 | 96 | pthread_condattr_t attr; |
75 | 96 | int r0 = pthread_condattr_init(&attr); |
76 | 96 | MOZ_RELEASE_ASSERT(!r0); |
77 | 96 | |
78 | 96 | int r1 = pthread_condattr_setclock(&attr, WhichClock); |
79 | 96 | MOZ_RELEASE_ASSERT(!r1); |
80 | 96 | |
81 | 96 | int r2 = pthread_cond_init(ptCond, &attr); |
82 | 96 | MOZ_RELEASE_ASSERT(!r2); |
83 | 96 | |
84 | 96 | int r3 = pthread_condattr_destroy(&attr); |
85 | 96 | MOZ_RELEASE_ASSERT(!r3); |
86 | | #else |
87 | | int r = pthread_cond_init(ptCond, NULL); |
88 | | MOZ_RELEASE_ASSERT(!r); |
89 | | #endif |
90 | | } |
91 | | |
92 | | mozilla::detail::ConditionVariableImpl::~ConditionVariableImpl() |
93 | 0 | { |
94 | 0 | int r = pthread_cond_destroy(&platformData()->ptCond); |
95 | 0 | MOZ_RELEASE_ASSERT(r == 0); |
96 | 0 | } |
97 | | |
98 | | void |
99 | | mozilla::detail::ConditionVariableImpl::notify_one() |
100 | 1.30k | { |
101 | 1.30k | int r = pthread_cond_signal(&platformData()->ptCond); |
102 | 1.30k | MOZ_RELEASE_ASSERT(r == 0); |
103 | 1.30k | } |
104 | | |
105 | | void |
106 | | mozilla::detail::ConditionVariableImpl::notify_all() |
107 | 1.02k | { |
108 | 1.02k | int r = pthread_cond_broadcast(&platformData()->ptCond); |
109 | 1.02k | MOZ_RELEASE_ASSERT(r == 0); |
110 | 1.02k | } |
111 | | |
112 | | void |
113 | | mozilla::detail::ConditionVariableImpl::wait(MutexImpl& lock) |
114 | 1.42k | { |
115 | 1.42k | pthread_cond_t* ptCond = &platformData()->ptCond; |
116 | 1.42k | pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex; |
117 | 1.42k | |
118 | 1.42k | int r = pthread_cond_wait(ptCond, ptMutex); |
119 | 1.42k | MOZ_RELEASE_ASSERT(r == 0); |
120 | 1.42k | } |
121 | | |
122 | | mozilla::CVStatus |
123 | | mozilla::detail::ConditionVariableImpl::wait_for(MutexImpl& lock, |
124 | | const TimeDuration& a_rel_time) |
125 | 1.50k | { |
126 | 1.50k | if (a_rel_time == TimeDuration::Forever()) { |
127 | 1.40k | wait(lock); |
128 | 1.40k | return CVStatus::NoTimeout; |
129 | 1.40k | } |
130 | 98 | |
131 | 98 | pthread_cond_t* ptCond = &platformData()->ptCond; |
132 | 98 | pthread_mutex_t* ptMutex = &lock.platformData()->ptMutex; |
133 | 98 | int r; |
134 | 98 | |
135 | 98 | // Clamp to 0, as time_t is unsigned. |
136 | 98 | TimeDuration rel_time = a_rel_time < TimeDuration::FromSeconds(0) |
137 | 98 | ? TimeDuration::FromSeconds(0) |
138 | 98 | : a_rel_time; |
139 | 98 | |
140 | 98 | // Convert the duration to a timespec. |
141 | 98 | struct timespec rel_ts; |
142 | 98 | rel_ts.tv_sec = static_cast<time_t>(rel_time.ToSeconds()); |
143 | 98 | rel_ts.tv_nsec = static_cast<uint64_t>(rel_time.ToMicroseconds() * 1000.0) % NanoSecPerSec; |
144 | 98 | |
145 | 98 | #ifdef CV_USE_CLOCK_API |
146 | 98 | struct timespec now_ts; |
147 | 98 | r = clock_gettime(WhichClock, &now_ts); |
148 | 98 | MOZ_RELEASE_ASSERT(!r); |
149 | 98 | |
150 | 98 | struct timespec abs_ts; |
151 | 98 | moz_timespecadd(&now_ts, &rel_ts, &abs_ts); |
152 | 98 | |
153 | 98 | r = pthread_cond_timedwait(ptCond, ptMutex, &abs_ts); |
154 | | #else |
155 | | // Our non-clock-supporting platforms, OS X and Android, do support waiting |
156 | | // on a condition variable with a relative timeout. |
157 | | r = pthread_cond_timedwait_relative_np(ptCond, ptMutex, &rel_ts); |
158 | | #endif |
159 | | |
160 | 98 | if (r == 0) { |
161 | 11 | return CVStatus::NoTimeout; |
162 | 11 | } |
163 | 87 | MOZ_RELEASE_ASSERT(r == ETIMEDOUT); |
164 | 87 | return CVStatus::Timeout; |
165 | 87 | } |
166 | | |
167 | | mozilla::detail::ConditionVariableImpl::PlatformData* |
168 | | mozilla::detail::ConditionVariableImpl::platformData() |
169 | 3.94k | { |
170 | 3.94k | static_assert(sizeof platformData_ >= sizeof(PlatformData), |
171 | 3.94k | "platformData_ is too small"); |
172 | 3.94k | return reinterpret_cast<PlatformData*>(platformData_); |
173 | 3.94k | } |