1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/synchronization/condition_variable.h"
6
7 #include <errno.h>
8 #include <stdint.h>
9 #include <sys/time.h>
10
11 #include <optional>
12
13 #include "base/synchronization/lock.h"
14 #include "base/threading/scoped_blocking_call.h"
15 #include "base/threading/thread_restrictions.h"
16 #include "base/time/time.h"
17 #include "build/build_config.h"
18
19 #if BUILDFLAG(IS_APPLE)
20 #include <atomic>
21
22 #include "base/feature_list.h"
23 #endif
24
25 #if BUILDFLAG(IS_ANDROID) && __ANDROID_API__ < 21
26 #define HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC 1
27 #endif
28
29 namespace {
30 #if BUILDFLAG(IS_APPLE)
31 // Under this feature a hack that was introduced to avoid crashes is skipped.
32 // Use to evaluate if the hack is still needed. See https://crbug.com/517681.
33 BASE_FEATURE(kSkipConditionVariableWakeupHack,
34 "SkipConditionVariableWakeupHack",
35 base::FEATURE_ENABLED_BY_DEFAULT);
36 std::atomic_bool g_skip_wakeup_hack = true;
37 #endif
38 } // namespace
39
40 namespace base {
41
ConditionVariable(Lock * user_lock)42 ConditionVariable::ConditionVariable(Lock* user_lock)
43 : user_mutex_(user_lock->lock_.native_handle())
44 #if DCHECK_IS_ON()
45 , user_lock_(user_lock)
46 #endif
47 {
48 int rv = 0;
49 // http://crbug.com/293736
50 // NaCl doesn't support monotonic clock based absolute deadlines.
51 // On older Android platform versions, it's supported through the
52 // non-standard pthread_cond_timedwait_monotonic_np. Newer platform
53 // versions have pthread_condattr_setclock.
54 // Mac can use relative time deadlines.
55 #if !BUILDFLAG(IS_APPLE) && !BUILDFLAG(IS_NACL) && \
56 !defined(HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC)
57 pthread_condattr_t attrs;
58 rv = pthread_condattr_init(&attrs);
59 DCHECK_EQ(0, rv);
60 pthread_condattr_setclock(&attrs, CLOCK_MONOTONIC);
61 rv = pthread_cond_init(&condition_, &attrs);
62 pthread_condattr_destroy(&attrs);
63 #else
64 rv = pthread_cond_init(&condition_, NULL);
65 #endif
66 DCHECK_EQ(0, rv);
67 }
68
~ConditionVariable()69 ConditionVariable::~ConditionVariable() {
70 #if BUILDFLAG(IS_APPLE)
71 // This hack is necessary to avoid a fatal pthreads subsystem bug in the
72 // Darwin kernel. http://crbug.com/517681.
73 if (!g_skip_wakeup_hack.load(std::memory_order_relaxed)) {
74 base::Lock lock;
75 base::AutoLock l(lock);
76 struct timespec ts;
77 ts.tv_sec = 0;
78 ts.tv_nsec = 1;
79 pthread_cond_timedwait_relative_np(&condition_, lock.lock_.native_handle(),
80 &ts);
81 }
82 #endif
83
84 int rv = pthread_cond_destroy(&condition_);
85 DCHECK_EQ(0, rv);
86 }
87
88 #if BUILDFLAG(IS_APPLE)
89 // static
InitializeFeatures()90 void ConditionVariable::InitializeFeatures() {
91 g_skip_wakeup_hack.store(
92 base::FeatureList::IsEnabled(kSkipConditionVariableWakeupHack),
93 std::memory_order_relaxed);
94 }
95 #endif
96
Wait()97 void ConditionVariable::Wait() {
98 std::optional<internal::ScopedBlockingCallWithBaseSyncPrimitives>
99 scoped_blocking_call;
100 if (waiting_is_blocking_)
101 scoped_blocking_call.emplace(FROM_HERE, BlockingType::MAY_BLOCK);
102
103 #if DCHECK_IS_ON()
104 user_lock_->CheckHeldAndUnmark();
105 #endif
106 int rv = pthread_cond_wait(&condition_, user_mutex_);
107 DCHECK_EQ(0, rv);
108 #if DCHECK_IS_ON()
109 user_lock_->CheckUnheldAndMark();
110 #endif
111 }
112
TimedWait(const TimeDelta & max_time)113 void ConditionVariable::TimedWait(const TimeDelta& max_time) {
114 std::optional<internal::ScopedBlockingCallWithBaseSyncPrimitives>
115 scoped_blocking_call;
116 if (waiting_is_blocking_)
117 scoped_blocking_call.emplace(FROM_HERE, BlockingType::MAY_BLOCK);
118
119 int64_t usecs = max_time.InMicroseconds();
120 struct timespec relative_time;
121 relative_time.tv_sec =
122 static_cast<time_t>(usecs / Time::kMicrosecondsPerSecond);
123 relative_time.tv_nsec =
124 (usecs % Time::kMicrosecondsPerSecond) * Time::kNanosecondsPerMicrosecond;
125
126 #if DCHECK_IS_ON()
127 user_lock_->CheckHeldAndUnmark();
128 #endif
129
130 #if BUILDFLAG(IS_APPLE)
131 int rv = pthread_cond_timedwait_relative_np(
132 &condition_, user_mutex_, &relative_time);
133 #else
134 // The timeout argument to pthread_cond_timedwait is in absolute time.
135 struct timespec absolute_time;
136 #if BUILDFLAG(IS_NACL)
137 // See comment in constructor for why this is different in NaCl.
138 struct timeval now;
139 gettimeofday(&now, NULL);
140 absolute_time.tv_sec = now.tv_sec;
141 absolute_time.tv_nsec = now.tv_usec * Time::kNanosecondsPerMicrosecond;
142 #else
143 struct timespec now;
144 clock_gettime(CLOCK_MONOTONIC, &now);
145 absolute_time.tv_sec = now.tv_sec;
146 absolute_time.tv_nsec = now.tv_nsec;
147 #endif
148
149 absolute_time.tv_sec += relative_time.tv_sec;
150 absolute_time.tv_nsec += relative_time.tv_nsec;
151 absolute_time.tv_sec += absolute_time.tv_nsec / Time::kNanosecondsPerSecond;
152 absolute_time.tv_nsec %= Time::kNanosecondsPerSecond;
153 DCHECK_GE(absolute_time.tv_sec, now.tv_sec); // Overflow paranoia
154
155 #if defined(HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC)
156 int rv = pthread_cond_timedwait_monotonic_np(
157 &condition_, user_mutex_, &absolute_time);
158 #else
159 int rv = pthread_cond_timedwait(&condition_, user_mutex_, &absolute_time);
160 #endif // HAVE_PTHREAD_COND_TIMEDWAIT_MONOTONIC
161 #endif // BUILDFLAG(IS_APPLE)
162
163 // On failure, we only expect the CV to timeout. Any other error value means
164 // that we've unexpectedly woken up.
165 DCHECK(rv == 0 || rv == ETIMEDOUT);
166 #if DCHECK_IS_ON()
167 user_lock_->CheckUnheldAndMark();
168 #endif
169 }
170
Broadcast()171 void ConditionVariable::Broadcast() {
172 int rv = pthread_cond_broadcast(&condition_);
173 DCHECK_EQ(0, rv);
174 }
175
Signal()176 void ConditionVariable::Signal() {
177 int rv = pthread_cond_signal(&condition_);
178 DCHECK_EQ(0, rv);
179 }
180
181 } // namespace base
182