/src/mozilla-central/mozglue/misc/Mutex_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/Maybe.h" |
9 | | |
10 | | #include <algorithm> |
11 | | #include <errno.h> |
12 | | #include <pthread.h> |
13 | | #include <stdio.h> |
14 | | #include <unistd.h> |
15 | | |
16 | | #include "mozilla/PlatformMutex.h" |
17 | | #include "MutexPlatformData_posix.h" |
18 | | |
19 | | #define REPORT_PTHREADS_ERROR(result, msg) \ |
20 | 0 | { \ |
21 | 0 | errno = result; \ |
22 | 0 | perror(msg); \ |
23 | 0 | MOZ_CRASH(msg); \ |
24 | 0 | } |
25 | | |
26 | | #define TRY_CALL_PTHREADS(call, msg) \ |
27 | 9.41M | { \ |
28 | 9.41M | int result = (call); \ |
29 | 9.41M | if (result != 0) { \ |
30 | 0 | REPORT_PTHREADS_ERROR(result, msg); \ |
31 | 0 | } \ |
32 | 9.41M | } |
33 | | |
34 | | #ifdef XP_DARWIN |
35 | | |
36 | | // CPU count. Read concurrently from multiple threads. Written once during the |
37 | | // first mutex initialization; re-initialization is safe hence relaxed ordering |
38 | | // is OK. |
39 | | static mozilla::Atomic<uint32_t, mozilla::MemoryOrdering::Relaxed, |
40 | | mozilla::recordreplay::Behavior::DontPreserve> sCPUCount(0); |
41 | | |
42 | | static void |
43 | | EnsureCPUCount() |
44 | | { |
45 | | if (sCPUCount) { |
46 | | return; |
47 | | } |
48 | | |
49 | | // _SC_NPROCESSORS_CONF and _SC_NPROCESSORS_ONLN are common, but not |
50 | | // standard. |
51 | | #if defined(_SC_NPROCESSORS_CONF) |
52 | | long n = sysconf(_SC_NPROCESSORS_CONF); |
53 | | sCPUCount = (n > 0) ? uint32_t(n) : 1; |
54 | | #elif defined(_SC_NPROCESSORS_ONLN) |
55 | | long n = sysconf(_SC_NPROCESSORS_ONLN); |
56 | | sCPUCount = (n > 0) ? uint32_t(n) : 1; |
57 | | #else |
58 | | sCPUCount = 1; |
59 | | #endif |
60 | | } |
61 | | |
62 | | #endif // XP_DARWIN |
63 | | |
64 | | mozilla::detail::MutexImpl::MutexImpl(recordreplay::Behavior aRecorded) |
65 | | #ifdef XP_DARWIN |
66 | | : averageSpins(0) |
67 | | #endif |
68 | 967 | { |
69 | 967 | pthread_mutexattr_t* attrp = nullptr; |
70 | 967 | |
71 | 967 | mozilla::Maybe<mozilla::recordreplay::AutoEnsurePassThroughThreadEvents> pt; |
72 | 967 | if (aRecorded == recordreplay::Behavior::DontPreserve) { |
73 | 229 | pt.emplace(); |
74 | 229 | } |
75 | 967 | |
76 | 967 | // Linux with glibc and FreeBSD support adaptive mutexes that spin |
77 | 967 | // for a short number of tries before sleeping. NSPR's locks did |
78 | 967 | // this, too, and it seems like a reasonable thing to do. |
79 | 967 | #if (defined(__linux__) && defined(__GLIBC__)) || defined(__FreeBSD__) |
80 | 967 | #define ADAPTIVE_MUTEX_SUPPORTED |
81 | 967 | #endif |
82 | 967 | |
83 | | #if defined(DEBUG) |
84 | | #define ATTR_REQUIRED |
85 | | #define MUTEX_KIND PTHREAD_MUTEX_ERRORCHECK |
86 | | #elif defined(ADAPTIVE_MUTEX_SUPPORTED) |
87 | | #define ATTR_REQUIRED |
88 | 967 | #define MUTEX_KIND PTHREAD_MUTEX_ADAPTIVE_NP |
89 | 967 | #endif |
90 | 967 | |
91 | 967 | #if defined(ATTR_REQUIRED) |
92 | 967 | pthread_mutexattr_t attr; |
93 | 967 | |
94 | 967 | TRY_CALL_PTHREADS(pthread_mutexattr_init(&attr), |
95 | 967 | "mozilla::detail::MutexImpl::MutexImpl: pthread_mutexattr_init failed"); |
96 | 967 | |
97 | 967 | TRY_CALL_PTHREADS(pthread_mutexattr_settype(&attr, MUTEX_KIND), |
98 | 967 | "mozilla::detail::MutexImpl::MutexImpl: pthread_mutexattr_settype failed"); |
99 | 967 | attrp = &attr; |
100 | 967 | #endif |
101 | 967 | |
102 | 967 | TRY_CALL_PTHREADS(pthread_mutex_init(&platformData()->ptMutex, attrp), |
103 | 967 | "mozilla::detail::MutexImpl::MutexImpl: pthread_mutex_init failed"); |
104 | 967 | |
105 | 967 | #if defined(ATTR_REQUIRED) |
106 | 967 | TRY_CALL_PTHREADS(pthread_mutexattr_destroy(&attr), |
107 | 967 | "mozilla::detail::MutexImpl::MutexImpl: pthread_mutexattr_destroy failed"); |
108 | 967 | #endif |
109 | 967 | |
110 | | #ifdef XP_DARWIN |
111 | | EnsureCPUCount(); |
112 | | #endif |
113 | | } |
114 | | |
115 | | mozilla::detail::MutexImpl::~MutexImpl() |
116 | 72 | { |
117 | 72 | TRY_CALL_PTHREADS(pthread_mutex_destroy(&platformData()->ptMutex), |
118 | 72 | "mozilla::detail::MutexImpl::~MutexImpl: pthread_mutex_destroy failed"); |
119 | 72 | } |
120 | | |
121 | | inline void |
122 | | mozilla::detail::MutexImpl::mutexLock() |
123 | 4.70M | { |
124 | 4.70M | TRY_CALL_PTHREADS(pthread_mutex_lock(&platformData()->ptMutex), |
125 | 4.70M | "mozilla::detail::MutexImpl::mutexLock: pthread_mutex_lock failed"); |
126 | 4.70M | } |
127 | | |
128 | | #ifdef XP_DARWIN |
129 | | inline bool |
130 | | mozilla::detail::MutexImpl::mutexTryLock() |
131 | | { |
132 | | int result = pthread_mutex_trylock(&platformData()->ptMutex); |
133 | | if (result == 0) { |
134 | | return true; |
135 | | } |
136 | | |
137 | | if (result == EBUSY) { |
138 | | return false; |
139 | | } |
140 | | |
141 | | REPORT_PTHREADS_ERROR(result, |
142 | | "mozilla::detail::MutexImpl::mutexTryLock: pthread_mutex_trylock failed"); |
143 | | } |
144 | | #endif |
145 | | |
146 | | void |
147 | | mozilla::detail::MutexImpl::lock() |
148 | 4.70M | { |
149 | 4.70M | #ifndef XP_DARWIN |
150 | 4.70M | mutexLock(); |
151 | | #else |
152 | | // Mutex performance on OSX can be very poor if there's a lot of contention as |
153 | | // this causes excessive context switching. On Linux/FreeBSD we use the |
154 | | // adaptive mutex type (PTHREAD_MUTEX_ADAPTIVE_NP) to address this, but this |
155 | | // isn't available on OSX. The code below is a reimplementation of this |
156 | | // feature. |
157 | | |
158 | | MOZ_ASSERT(sCPUCount); |
159 | | if (sCPUCount == 1 || recordreplay::IsRecordingOrReplaying()) { |
160 | | mutexLock(); |
161 | | return; |
162 | | } |
163 | | |
164 | | if (!mutexTryLock()) { |
165 | | const int32_t SpinLimit = 100; |
166 | | |
167 | | int32_t count = 0; |
168 | | int32_t maxSpins = std::min(SpinLimit, 2 * averageSpins + 10); |
169 | | do { |
170 | | if (count >= maxSpins) { |
171 | | mutexLock(); |
172 | | break; |
173 | | } |
174 | | asm("pause"); // Hint to the processor that we're spinning. |
175 | | count++; |
176 | | } while (!mutexTryLock()); |
177 | | |
178 | | // Update moving average. |
179 | | averageSpins += (count - averageSpins) / 8; |
180 | | MOZ_ASSERT(averageSpins >= 0 && averageSpins <= SpinLimit); |
181 | | } |
182 | | #endif // XP_DARWIN |
183 | | } |
184 | | |
185 | | void |
186 | | mozilla::detail::MutexImpl::unlock() |
187 | 4.70M | { |
188 | 4.70M | TRY_CALL_PTHREADS(pthread_mutex_unlock(&platformData()->ptMutex), |
189 | 4.70M | "mozilla::detail::MutexImpl::unlock: pthread_mutex_unlock failed"); |
190 | 4.70M | } |
191 | | |
192 | | #undef TRY_CALL_PTHREADS |
193 | | |
194 | | mozilla::detail::MutexImpl::PlatformData* |
195 | | mozilla::detail::MutexImpl::platformData() |
196 | 9.40M | { |
197 | 9.40M | static_assert(sizeof(platformData_) >= sizeof(PlatformData), |
198 | 9.40M | "platformData_ is too small"); |
199 | 9.40M | return reinterpret_cast<PlatformData*>(platformData_); |
200 | 9.40M | } |