tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

Mutex.h (8301B)


      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 #ifndef Mutex_h
      8 #define Mutex_h
      9 
     10 #if defined(XP_WIN)
     11 #  include <windows.h>
     12 #else
     13 #  include <pthread.h>
     14 #endif
     15 #if defined(XP_DARWIN)
     16 #  include <os/lock.h>
     17 #endif
     18 
     19 #include "mozilla/Assertions.h"
     20 #include "mozilla/Attributes.h"
     21 #include "mozilla/MaybeStorageBase.h"
     22 #include "mozilla/ThreadSafety.h"
     23 
     24 #if defined(XP_DARWIN)
     25 // For information about the following undocumented flags and functions see
     26 // https://github.com/apple/darwin-xnu/blob/main/bsd/sys/ulock.h and
     27 // https://github.com/apple/darwin-libplatform/blob/main/private/os/lock_private.h
     28 #  define OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION (0x00010000)
     29 #  define OS_UNFAIR_LOCK_ADAPTIVE_SPIN (0x00040000)
     30 
     31 extern "C" {
     32 
     33 typedef uint32_t os_unfair_lock_options_t;
     34 OS_UNFAIR_LOCK_AVAILABILITY
     35 OS_EXPORT OS_NOTHROW OS_NONNULL_ALL void os_unfair_lock_lock_with_options(
     36    os_unfair_lock_t lock, os_unfair_lock_options_t options);
     37 }
     38 #endif  // defined(XP_DARWIN)
     39 
     40 // Mutexes are based on spinlocks.  We can't use normal pthread spinlocks in all
     41 // places, because they require malloc()ed memory, which causes bootstrapping
     42 // issues in some cases.  We also can't use non-constexpr constructors, because
     43 // for statics, they would fire after the first use of malloc, resetting the
     44 // locks.
     45 //
     46 // A constexpr constructor is provided so that Mutex can be part of something
     47 // that is constinit, but the mutex won't be initialised, you must still
     48 // call Init() before the mutex can be used.
     49 struct MOZ_CAPABILITY("mutex") Mutex {
     50 #if defined(XP_WIN)
     51  // MaybeStorageBase provides a constexpr constructor.
     52  mozilla::detail::MaybeStorageBase<CRITICAL_SECTION> mMutex;
     53 #elif defined(XP_DARWIN)
     54  os_unfair_lock mMutex = OS_UNFAIR_LOCK_INIT;
     55 #elif defined(XP_LINUX) && !defined(ANDROID)
     56  pthread_mutex_t mMutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;
     57 #else
     58  pthread_mutex_t mMutex = PTHREAD_MUTEX_INITIALIZER;
     59 #endif
     60 
     61 #ifdef MOZ_DEBUG
     62  bool mInitialised = false;
     63 
     64  // Called by StaticMutex
     65  explicit constexpr Mutex(bool aInitialised) : mInitialised(aInitialised) {}
     66 #else
     67  explicit constexpr Mutex(bool aIgnored) {}
     68 #endif
     69 
     70  // Although a constexpr constructor is provided, it will not initialise the
     71  // mutex and calling Init() is required.
     72  constexpr Mutex() {}
     73 
     74  // (Re-)initializes a mutex. Returns whether initialization succeeded.
     75  inline bool Init() {
     76 #ifdef MOZ_DEBUG
     77    mInitialised = true;
     78 #endif
     79 #if defined(XP_WIN)
     80    if (!InitializeCriticalSectionAndSpinCount(mMutex.addr(), 5000)) {
     81      return false;
     82    }
     83 #elif defined(XP_DARWIN)
     84    mMutex = OS_UNFAIR_LOCK_INIT;
     85 #elif defined(XP_LINUX) && !defined(ANDROID)
     86    pthread_mutexattr_t attr;
     87    if (pthread_mutexattr_init(&attr) != 0) {
     88      return false;
     89    }
     90    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ADAPTIVE_NP);
     91    if (pthread_mutex_init(&mMutex, &attr) != 0) {
     92      pthread_mutexattr_destroy(&attr);
     93      return false;
     94    }
     95    pthread_mutexattr_destroy(&attr);
     96 #else
     97    if (pthread_mutex_init(&mMutex, nullptr) != 0) {
     98      return false;
     99    }
    100 #endif
    101    return true;
    102  }
    103 
    104  inline void Lock() MOZ_CAPABILITY_ACQUIRE() {
    105    MOZ_ASSERT(mInitialised);
    106 
    107 #if defined(XP_WIN)
    108    EnterCriticalSection(mMutex.addr());
    109 #elif defined(XP_DARWIN)
    110    // We rely on a non-public function to improve performance here.
    111    // The OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION flag informs the kernel that
    112    // the calling thread is able to make progress even in absence of actions
    113    // from other threads and the OS_UNFAIR_LOCK_ADAPTIVE_SPIN one causes the
    114    // kernel to spin on a contested lock if the owning thread is running on
    115    // the same physical core (presumably only on x86 CPUs given that ARM
    116    // macs don't have cores capable of SMT).
    117    os_unfair_lock_lock_with_options(
    118        &mMutex,
    119        OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION | OS_UNFAIR_LOCK_ADAPTIVE_SPIN);
    120 #else
    121    pthread_mutex_lock(&mMutex);
    122 #endif
    123  }
    124 
    125  [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true);
    126 
    127  inline void Unlock() MOZ_CAPABILITY_RELEASE() {
    128    MOZ_ASSERT(mInitialised);
    129 
    130 #if defined(XP_WIN)
    131    LeaveCriticalSection(mMutex.addr());
    132 #elif defined(XP_DARWIN)
    133    os_unfair_lock_unlock(&mMutex);
    134 #else
    135    pthread_mutex_unlock(&mMutex);
    136 #endif
    137  }
    138 
    139 #if defined(XP_DARWIN)
    140  static bool SpinInKernelSpace();
    141  static const bool gSpinInKernelSpace;
    142 #endif  // XP_DARWIN
    143 };
    144 
    145 // Mutex that can be used for static initialization.
    146 // On Windows, CRITICAL_SECTION requires a function call to be initialized,
    147 // but for the initialization lock, a static initializer calling the
    148 // function would be called too late. We need no-function-call
    149 // initialization, which SRWLock provides.
    150 // Ideally, we'd use the same type of locks everywhere, but SRWLocks
    151 // everywhere incur a performance penalty. See bug 1418389.
    152 #if defined(XP_WIN)
    153 struct MOZ_CAPABILITY("mutex") StaticMutex {
    154  SRWLOCK mMutex;
    155 
    156  constexpr StaticMutex() : mMutex(SRWLOCK_INIT) {}
    157 
    158  inline void Lock() MOZ_CAPABILITY_ACQUIRE() {
    159    AcquireSRWLockExclusive(&mMutex);
    160  }
    161 
    162  inline void Unlock() MOZ_CAPABILITY_RELEASE() {
    163    ReleaseSRWLockExclusive(&mMutex);
    164  }
    165 };
    166 
    167 #else
    168 struct MOZ_CAPABILITY("mutex") StaticMutex : public Mutex {
    169  constexpr StaticMutex() : Mutex(true) {}
    170 };
    171 #endif
    172 
    173 #ifdef XP_WIN
    174 typedef DWORD ThreadId;
    175 inline ThreadId GetThreadId() { return GetCurrentThreadId(); }
    176 inline bool ThreadIdEqual(ThreadId a, ThreadId b) { return a == b; }
    177 #else
    178 typedef pthread_t ThreadId;
    179 inline ThreadId GetThreadId() { return pthread_self(); }
    180 inline bool ThreadIdEqual(ThreadId a, ThreadId b) {
    181  return pthread_equal(a, b);
    182 }
    183 #endif
    184 
    185 class MOZ_CAPABILITY("mutex") MaybeMutex : public Mutex {
    186 public:
    187  enum DoLock {
    188    MUST_LOCK,
    189    AVOID_LOCK_UNSAFE,
    190  };
    191 
    192  bool Init(DoLock aDoLock) {
    193    mDoLock = aDoLock;
    194 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    195    mThreadId = GetThreadId();
    196 #endif
    197    return Mutex::Init();
    198  }
    199 
    200 #ifndef XP_WIN
    201  // Re initialise after fork(), assumes that mDoLock is already initialised.
    202  void Reinit(pthread_t aForkingThread) {
    203    if (mDoLock == MUST_LOCK) {
    204      Mutex::Init();
    205      return;
    206    }
    207 #  ifdef MOZ_DEBUG
    208    // If this is an eluded lock we can only safely re-initialise it if the
    209    // thread that called fork is the one that owns the lock.
    210    if (pthread_equal(mThreadId, aForkingThread)) {
    211      mThreadId = GetThreadId();
    212      Mutex::Init();
    213    } else {
    214      // We can't guantee that whatever resource this lock protects (probably a
    215      // jemalloc arena) is in a consistent state.
    216      mDeniedAfterFork = true;
    217    }
    218 #  endif
    219  }
    220 #endif
    221 
    222  inline void Lock() MOZ_CAPABILITY_ACQUIRE() {
    223    if (ShouldLock()) {
    224      Mutex::Lock();
    225    }
    226  }
    227 
    228  inline void Unlock() MOZ_CAPABILITY_RELEASE() {
    229    if (ShouldLock()) {
    230      Mutex::Unlock();
    231    }
    232  }
    233 
    234  // Return true if we can use this resource from this thread, either because
    235  // we'll use the lock or because this is the only thread that will access the
    236  // protected resource.
    237 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    238  bool SafeOnThisThread() const {
    239    return mDoLock == MUST_LOCK || ThreadIdEqual(GetThreadId(), mThreadId);
    240  }
    241 #endif
    242 
    243  bool LockIsEnabled() const { return mDoLock == MUST_LOCK; }
    244 
    245 private:
    246  bool ShouldLock() {
    247 #ifndef XP_WIN
    248    MOZ_ASSERT(!mDeniedAfterFork);
    249 #endif
    250 
    251    if (mDoLock == MUST_LOCK) {
    252      return true;
    253    }
    254 
    255    MOZ_ASSERT(SafeOnThisThread());
    256    return false;
    257  }
    258 
    259  DoLock mDoLock;
    260 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
    261  ThreadId mThreadId;
    262 #endif
    263 #if (!defined(XP_WIN) && defined(DEBUG))
    264  bool mDeniedAfterFork = false;
    265 #endif
    266 };
    267 
    268 template <typename T>
    269 struct MOZ_SCOPED_CAPABILITY MOZ_RAII AutoLock {
    270  explicit AutoLock(T& aMutex) MOZ_CAPABILITY_ACQUIRE(aMutex) : mMutex(aMutex) {
    271    mMutex.Lock();
    272  }
    273 
    274  ~AutoLock() MOZ_CAPABILITY_RELEASE() { mMutex.Unlock(); }
    275 
    276  AutoLock(const AutoLock&) = delete;
    277  AutoLock(AutoLock&&) = delete;
    278 
    279 private:
    280  T& mMutex;
    281 };
    282 
    283 using MutexAutoLock = AutoLock<Mutex>;
    284 
    285 using MaybeMutexAutoLock = AutoLock<MaybeMutex>;
    286 
    287 extern StaticMutex gInitLock MOZ_UNANNOTATED;
    288 
    289 #endif