• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef V8_BASE_PLATFORM_MUTEX_H_
6 #define V8_BASE_PLATFORM_MUTEX_H_
7 
8 #include "src/base/base-export.h"
9 #include "src/base/lazy-instance.h"
10 #include "src/base/optional.h"
11 #if V8_OS_WIN
12 #include "src/base/win32-headers.h"
13 #endif
14 #include "src/base/logging.h"
15 
16 #if V8_OS_POSIX
17 #include <pthread.h>
18 #endif
19 
20 #if V8_OS_STARBOARD
21 #include "starboard/common/mutex.h"
22 #include "starboard/common/recursive_mutex.h"
23 #include "starboard/common/rwlock.h"
24 #endif
25 
26 namespace v8 {
27 namespace base {
28 
29 // ----------------------------------------------------------------------------
30 // Mutex - a replacement for std::mutex
31 //
32 // This class is a synchronization primitive that can be used to protect shared
33 // data from being simultaneously accessed by multiple threads. A mutex offers
34 // exclusive, non-recursive ownership semantics:
35 // - A calling thread owns a mutex from the time that it successfully calls
36 //   either |Lock()| or |TryLock()| until it calls |Unlock()|.
37 // - When a thread owns a mutex, all other threads will block (for calls to
38 //   |Lock()|) or receive a |false| return value (for |TryLock()|) if they
39 //   attempt to claim ownership of the mutex.
40 // A calling thread must not own the mutex prior to calling |Lock()| or
41 // |TryLock()|. The behavior of a program is undefined if a mutex is destroyed
42 // while still owned by some thread. The Mutex class is non-copyable.
43 
44 class V8_BASE_EXPORT Mutex final {
45  public:
46   Mutex();
47   Mutex(const Mutex&) = delete;
48   Mutex& operator=(const Mutex&) = delete;
49   ~Mutex();
50 
51   // Locks the given mutex. If the mutex is currently unlocked, it becomes
52   // locked and owned by the calling thread, and immediately. If the mutex
53   // is already locked by another thread, suspends the calling thread until
54   // the mutex is unlocked.
55   void Lock();
56 
57   // Unlocks the given mutex. The mutex is assumed to be locked and owned by
58   // the calling thread on entrance.
59   void Unlock();
60 
61   // Tries to lock the given mutex. Returns whether the mutex was
62   // successfully locked.
63   bool TryLock() V8_WARN_UNUSED_RESULT;
64 
65   // The implementation-defined native handle type.
66 #if V8_OS_POSIX
67   using NativeHandle = pthread_mutex_t;
68 #elif V8_OS_WIN
69   using NativeHandle = V8_SRWLOCK;
70 #elif V8_OS_STARBOARD
71   using NativeHandle = SbMutex;
72 #endif
73 
native_handle()74   NativeHandle& native_handle() {
75     return native_handle_;
76   }
native_handle()77   const NativeHandle& native_handle() const {
78     return native_handle_;
79   }
80 
AssertHeld()81   V8_INLINE void AssertHeld() const { DCHECK_EQ(1, level_); }
AssertUnheld()82   V8_INLINE void AssertUnheld() const { DCHECK_EQ(0, level_); }
83 
84  private:
85   NativeHandle native_handle_;
86 #ifdef DEBUG
87   int level_;
88 #endif
89 
AssertHeldAndUnmark()90   V8_INLINE void AssertHeldAndUnmark() {
91 #ifdef DEBUG
92     DCHECK_EQ(1, level_);
93     level_--;
94 #endif
95   }
96 
AssertUnheldAndMark()97   V8_INLINE void AssertUnheldAndMark() {
98 #ifdef DEBUG
99     DCHECK_EQ(0, level_);
100     level_++;
101 #endif
102   }
103 
104   friend class ConditionVariable;
105 };
106 
107 // POD Mutex initialized lazily (i.e. the first time Pointer() is called).
108 // Usage:
109 //   static LazyMutex my_mutex = LAZY_MUTEX_INITIALIZER;
110 //
111 //   void my_function() {
112 //     MutexGuard guard(my_mutex.Pointer());
113 //     // Do something.
114 //   }
115 //
116 using LazyMutex = LazyStaticInstance<Mutex, DefaultConstructTrait<Mutex>,
117                                      ThreadSafeInitOnceTrait>::type;
118 
119 #define LAZY_MUTEX_INITIALIZER LAZY_STATIC_INSTANCE_INITIALIZER
120 
121 // -----------------------------------------------------------------------------
122 // RecursiveMutex - a replacement for std::recursive_mutex
123 //
124 // This class is a synchronization primitive that can be used to protect shared
125 // data from being simultaneously accessed by multiple threads. A recursive
126 // mutex offers exclusive, recursive ownership semantics:
127 // - A calling thread owns a recursive mutex for a period of time that starts
128 //   when it successfully calls either |Lock()| or |TryLock()|. During this
129 //   period, the thread may make additional calls to |Lock()| or |TryLock()|.
130 //   The period of ownership ends when the thread makes a matching number of
131 //   calls to |Unlock()|.
132 // - When a thread owns a recursive mutex, all other threads will block (for
133 //   calls to |Lock()|) or receive a |false| return value (for |TryLock()|) if
134 //   they attempt to claim ownership of the recursive mutex.
135 // - The maximum number of times that a recursive mutex may be locked is
136 //   unspecified, but after that number is reached, calls to |Lock()| will
137 //   probably abort the process and calls to |TryLock()| return false.
138 // The behavior of a program is undefined if a recursive mutex is destroyed
139 // while still owned by some thread. The RecursiveMutex class is non-copyable.
140 
141 class V8_BASE_EXPORT RecursiveMutex final {
142  public:
143   RecursiveMutex();
144   RecursiveMutex(const RecursiveMutex&) = delete;
145   RecursiveMutex& operator=(const RecursiveMutex&) = delete;
146   ~RecursiveMutex();
147 
148   // Locks the mutex. If another thread has already locked the mutex, a call to
149   // |Lock()| will block execution until the lock is acquired. A thread may call
150   // |Lock()| on a recursive mutex repeatedly. Ownership will only be released
151   // after the thread makes a matching number of calls to |Unlock()|.
152   // The behavior is undefined if the mutex is not unlocked before being
153   // destroyed, i.e. some thread still owns it.
154   void Lock();
155 
156   // Unlocks the mutex if its level of ownership is 1 (there was exactly one
157   // more call to |Lock()| than there were calls to unlock() made by this
158   // thread), reduces the level of ownership by 1 otherwise. The mutex must be
159   // locked by the current thread of execution, otherwise, the behavior is
160   // undefined.
161   void Unlock();
162 
163   // Tries to lock the given mutex. Returns whether the mutex was
164   // successfully locked.
165   bool TryLock() V8_WARN_UNUSED_RESULT;
166 
AssertHeld()167   V8_INLINE void AssertHeld() const { DCHECK_LT(0, level_); }
168 
169  private:
170   // The implementation-defined native handle type.
171 #if V8_OS_POSIX
172   using NativeHandle = pthread_mutex_t;
173 #elif V8_OS_WIN
174   using NativeHandle = V8_CRITICAL_SECTION;
175 #elif V8_OS_STARBOARD
176   using NativeHandle = starboard::RecursiveMutex;
177 #endif
178 
179   NativeHandle native_handle_;
180 #ifdef DEBUG
181   int level_;
182 #endif
183 };
184 
185 
186 // POD RecursiveMutex initialized lazily (i.e. the first time Pointer() is
187 // called).
188 // Usage:
189 //   static LazyRecursiveMutex my_mutex = LAZY_RECURSIVE_MUTEX_INITIALIZER;
190 //
191 //   void my_function() {
192 //     LockGuard<RecursiveMutex> guard(my_mutex.Pointer());
193 //     // Do something.
194 //   }
195 //
196 using LazyRecursiveMutex =
197     LazyStaticInstance<RecursiveMutex, DefaultConstructTrait<RecursiveMutex>,
198                        ThreadSafeInitOnceTrait>::type;
199 
200 #define LAZY_RECURSIVE_MUTEX_INITIALIZER LAZY_STATIC_INSTANCE_INITIALIZER
201 
202 // ----------------------------------------------------------------------------
203 // SharedMutex - a replacement for std::shared_mutex
204 //
205 // This class is a synchronization primitive that can be used to protect shared
206 // data from being simultaneously accessed by multiple threads. In contrast to
207 // other mutex types which facilitate exclusive access, a shared_mutex has two
208 // levels of access:
209 // - shared: several threads can share ownership of the same mutex.
210 // - exclusive: only one thread can own the mutex.
211 // Shared mutexes are usually used in situations when multiple readers can
212 // access the same resource at the same time without causing data races, but
213 // only one writer can do so.
214 // The SharedMutex class is non-copyable.
215 
216 class V8_BASE_EXPORT SharedMutex final {
217  public:
218   SharedMutex();
219   SharedMutex(const SharedMutex&) = delete;
220   SharedMutex& operator=(const SharedMutex&) = delete;
221   ~SharedMutex();
222 
223   // Acquires shared ownership of the {SharedMutex}. If another thread is
224   // holding the mutex in exclusive ownership, a call to {LockShared()} will
225   // block execution until shared ownership can be acquired.
226   // If {LockShared()} is called by a thread that already owns the mutex in any
227   // mode (exclusive or shared), the behavior is undefined and outright fails
228   // with dchecks on.
229   void LockShared();
230 
231   // Locks the SharedMutex. If another thread has already locked the mutex, a
232   // call to {LockExclusive()} will block execution until the lock is acquired.
233   // If {LockExclusive()} is called by a thread that already owns the mutex in
234   // any mode (shared or exclusive), the behavior is undefined and outright
235   // fails with dchecks on.
236   void LockExclusive();
237 
238   // Releases the {SharedMutex} from shared ownership by the calling thread.
239   // The mutex must be locked by the current thread of execution in shared mode,
240   // otherwise, the behavior is undefined and outright fails with dchecks on.
241   void UnlockShared();
242 
243   // Unlocks the {SharedMutex}. It must be locked by the current thread of
244   // execution, otherwise, the behavior is undefined and outright fails with
245   // dchecks on.
246   void UnlockExclusive();
247 
248   // Tries to lock the {SharedMutex} in shared mode. Returns immediately. On
249   // successful lock acquisition returns true, otherwise returns false.
250   // This function is allowed to fail spuriously and return false even if the
251   // mutex is not currenly exclusively locked by any other thread.
252   // If it is called by a thread that already owns the mutex in any mode
253   // (shared or exclusive), the behavior is undefined, and outright fails with
254   // dchecks on.
255   bool TryLockShared() V8_WARN_UNUSED_RESULT;
256 
257   // Tries to lock the {SharedMutex}. Returns immediately. On successful lock
258   // acquisition returns true, otherwise returns false.
259   // This function is allowed to fail spuriously and return false even if the
260   // mutex is not currently locked by any other thread.
261   // If it is called by a thread that already owns the mutex in any mode
262   // (shared or exclusive), the behavior is undefined, and outright fails with
263   // dchecks on.
264   bool TryLockExclusive() V8_WARN_UNUSED_RESULT;
265 
266  private:
267   // The implementation-defined native handle type.
268 #if V8_OS_DARWIN
269   // pthread_rwlock_t is broken on MacOS when signals are being sent to the
270   // process (see https://crbug.com/v8/11399). Until Apple fixes that in the OS,
271   // we have to fall back to a non-shared mutex.
272   using NativeHandle = pthread_mutex_t;
273 #elif V8_OS_POSIX
274   using NativeHandle = pthread_rwlock_t;
275 #elif V8_OS_WIN
276   using NativeHandle = V8_SRWLOCK;
277 #elif V8_OS_STARBOARD
278   using NativeHandle = starboard::RWLock;
279 #endif
280 
281   NativeHandle native_handle_;
282 };
283 
284 // -----------------------------------------------------------------------------
285 // LockGuard
286 //
287 // This class is a mutex wrapper that provides a convenient RAII-style mechanism
288 // for owning a mutex for the duration of a scoped block.
289 // When a LockGuard object is created, it attempts to take ownership of the
290 // mutex it is given. When control leaves the scope in which the LockGuard
291 // object was created, the LockGuard is destructed and the mutex is released.
292 // The LockGuard class is non-copyable.
293 
294 // Controls whether a LockGuard always requires a valid Mutex or will just
295 // ignore it if it's nullptr.
296 enum class NullBehavior { kRequireNotNull, kIgnoreIfNull };
297 
298 template <typename Mutex, NullBehavior Behavior = NullBehavior::kRequireNotNull>
299 class V8_NODISCARD LockGuard final {
300  public:
LockGuard(Mutex * mutex)301   explicit LockGuard(Mutex* mutex) : mutex_(mutex) {
302     if (has_mutex()) mutex_->Lock();
303   }
304   LockGuard(const LockGuard&) = delete;
305   LockGuard& operator=(const LockGuard&) = delete;
~LockGuard()306   ~LockGuard() {
307     if (has_mutex()) mutex_->Unlock();
308   }
309 
310  private:
311   Mutex* const mutex_;
312 
has_mutex()313   bool V8_INLINE has_mutex() const {
314     DCHECK_IMPLIES(Behavior == NullBehavior::kRequireNotNull,
315                    mutex_ != nullptr);
316     return Behavior == NullBehavior::kRequireNotNull || mutex_ != nullptr;
317   }
318 };
319 
320 using MutexGuard = LockGuard<Mutex>;
321 using RecursiveMutexGuard = LockGuard<RecursiveMutex>;
322 
323 enum MutexSharedType : bool { kShared = true, kExclusive = false };
324 
325 template <MutexSharedType kIsShared,
326           NullBehavior Behavior = NullBehavior::kRequireNotNull>
327 class V8_NODISCARD SharedMutexGuard final {
328  public:
SharedMutexGuard(SharedMutex * mutex)329   explicit SharedMutexGuard(SharedMutex* mutex) : mutex_(mutex) {
330     if (!has_mutex()) return;
331     if (kIsShared) {
332       mutex_->LockShared();
333     } else {
334       mutex_->LockExclusive();
335     }
336   }
337   SharedMutexGuard(const SharedMutexGuard&) = delete;
338   SharedMutexGuard& operator=(const SharedMutexGuard&) = delete;
~SharedMutexGuard()339   ~SharedMutexGuard() {
340     if (!has_mutex()) return;
341     if (kIsShared) {
342       mutex_->UnlockShared();
343     } else {
344       mutex_->UnlockExclusive();
345     }
346   }
347 
348  private:
349   SharedMutex* const mutex_;
350 
has_mutex()351   bool V8_INLINE has_mutex() const {
352     DCHECK_IMPLIES(Behavior == NullBehavior::kRequireNotNull,
353                    mutex_ != nullptr);
354     return Behavior == NullBehavior::kRequireNotNull || mutex_ != nullptr;
355   }
356 };
357 
358 template <MutexSharedType kIsShared,
359           NullBehavior Behavior = NullBehavior::kRequireNotNull>
360 class V8_NODISCARD SharedMutexGuardIf final {
361  public:
SharedMutexGuardIf(SharedMutex * mutex,bool enable_mutex)362   SharedMutexGuardIf(SharedMutex* mutex, bool enable_mutex) {
363     if (enable_mutex) mutex_.emplace(mutex);
364   }
365   SharedMutexGuardIf(const SharedMutexGuardIf&) = delete;
366   SharedMutexGuardIf& operator=(const SharedMutexGuardIf&) = delete;
367 
368  private:
369   base::Optional<SharedMutexGuard<kIsShared, Behavior>> mutex_;
370 };
371 
372 }  // namespace base
373 }  // namespace v8
374 
375 #endif  // V8_BASE_PLATFORM_MUTEX_H_
376