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 threading_ExclusiveData_h 8 #define threading_ExclusiveData_h 9 10 #include "mozilla/Maybe.h" 11 #include "mozilla/OperatorNewExtensions.h" 12 13 #include <utility> 14 15 #include "threading/ConditionVariable.h" 16 #include "threading/Mutex.h" 17 18 namespace js { 19 20 /** 21 * [SMDOC] ExclusiveData API 22 * 23 * A mutual exclusion lock class. 24 * 25 * `ExclusiveData` provides an RAII guard to automatically lock and unlock when 26 * accessing the protected inner value. 27 * 28 * Unlike the STL's `std::mutex`, the protected value is internal to this 29 * class. This is a huge win: one no longer has to rely on documentation to 30 * explain the relationship between a lock and its protected data, and the type 31 * system can enforce[0] it. 32 * 33 * For example, suppose we have a counter class: 34 * 35 * class Counter 36 * { 37 * int32_t i; 38 * 39 * public: 40 * void inc(int32_t n) { i += n; } 41 * }; 42 * 43 * If we share a counter across threads with `std::mutex`, we rely solely on 44 * comments to document the relationship between the lock and its data, like 45 * this: 46 * 47 * class SharedCounter 48 * { 49 * // Remember to acquire `counter_lock` when accessing `counter`, 50 * // pretty please! 51 * Counter counter; 52 * std::mutex counter_lock; 53 * 54 * public: 55 * void inc(size_t n) { 56 * // Whoops, forgot to acquire the lock! Off to the races! 57 * counter.inc(n); 58 * } 59 * }; 60 * 61 * In contrast, `ExclusiveData` wraps the protected value, enabling the type 62 * system to enforce that we acquire the lock before accessing the value: 63 * 64 * class SharedCounter 65 * { 66 * ExclusiveData<Counter> counter; 67 * 68 * public: 69 * void inc(size_t n) { 70 * auto guard = counter.lock(); 71 * guard->inc(n); 72 * } 73 * }; 74 * 75 * The API design is based on Rust's `std::sync::Mutex<T>` type. 76 * 77 * [0]: Of course, we don't have a borrow checker in C++, so the type system 78 * cannot guarantee that you don't stash references received from 79 * `ExclusiveData<T>::Guard` somewhere such that the reference outlives the 80 * guard's lifetime and therefore becomes invalid. To help avoid this last 81 * foot-gun, prefer using the guard directly! Do not store raw references 82 * to the protected value in other structures! 83 */ 84 template <typename T> 85 class ExclusiveData { 86 protected: 87 mutable Mutex lock_; 88 mutable T value_; 89 90 ExclusiveData(const ExclusiveData&) = delete; 91 ExclusiveData& operator=(const ExclusiveData&) = delete; 92 acquire()93 void acquire() const { lock_.lock(); } release()94 void release() const { lock_.unlock(); } 95 96 public: 97 /** 98 * Create a new `ExclusiveData`, with perfect forwarding of the protected 99 * value. 100 */ 101 template <typename U> ExclusiveData(const MutexId & id,U && u)102 explicit ExclusiveData(const MutexId& id, U&& u) 103 : lock_(id), value_(std::forward<U>(u)) {} 104 105 /** 106 * Create a new `ExclusiveData`, constructing the protected value in place. 107 */ 108 template <typename... Args> ExclusiveData(const MutexId & id,Args &&...args)109 explicit ExclusiveData(const MutexId& id, Args&&... args) 110 : lock_(id), value_(std::forward<Args>(args)...) {} 111 ExclusiveData(ExclusiveData && rhs)112 ExclusiveData(ExclusiveData&& rhs) 113 : lock_(std::move(rhs.lock)), value_(std::move(rhs.value_)) { 114 MOZ_ASSERT(&rhs != this, "self-move disallowed!"); 115 } 116 117 ExclusiveData& operator=(ExclusiveData&& rhs) { 118 this->~ExclusiveData(); 119 new (mozilla::KnownNotNull, this) ExclusiveData(std::move(rhs)); 120 return *this; 121 } 122 123 /** 124 * An RAII class that provides exclusive access to a `ExclusiveData<T>`'s 125 * protected inner `T` value. 126 * 127 * Note that this is intentionally marked MOZ_STACK_CLASS instead of 128 * MOZ_RAII_CLASS, as the latter disallows moves and returning by value, but 129 * Guard utilizes both. 130 */ 131 class MOZ_STACK_CLASS Guard { 132 const ExclusiveData* parent_; 133 134 Guard(const Guard&) = delete; 135 Guard& operator=(const Guard&) = delete; 136 137 public: Guard(const ExclusiveData & parent)138 explicit Guard(const ExclusiveData& parent) : parent_(&parent) { 139 parent_->acquire(); 140 } 141 Guard(Guard && rhs)142 Guard(Guard&& rhs) : parent_(rhs.parent_) { 143 MOZ_ASSERT(&rhs != this, "self-move disallowed!"); 144 rhs.parent_ = nullptr; 145 } 146 147 Guard& operator=(Guard&& rhs) { 148 this->~Guard(); 149 new (this) Guard(std::move(rhs)); 150 return *this; 151 } 152 get()153 T& get() const { 154 MOZ_ASSERT(parent_); 155 return parent_->value_; 156 } 157 158 operator T&() const { return get(); } 159 T* operator->() const { return &get(); } 160 parent()161 const ExclusiveData<T>* parent() const { 162 MOZ_ASSERT(parent_); 163 return parent_; 164 } 165 ~Guard()166 ~Guard() { 167 if (parent_) { 168 parent_->release(); 169 } 170 } 171 }; 172 173 /** 174 * Access the protected inner `T` value for exclusive reading and writing. 175 */ lock()176 Guard lock() const { return Guard(*this); } 177 }; 178 179 template <class T> 180 class ExclusiveWaitableData : public ExclusiveData<T> { 181 using Base = ExclusiveData<T>; 182 183 mutable ConditionVariable condVar_; 184 185 public: 186 template <typename U> ExclusiveWaitableData(const MutexId & id,U && u)187 explicit ExclusiveWaitableData(const MutexId& id, U&& u) 188 : Base(id, std::forward<U>(u)) {} 189 190 template <typename... Args> ExclusiveWaitableData(const MutexId & id,Args &&...args)191 explicit ExclusiveWaitableData(const MutexId& id, Args&&... args) 192 : Base(id, std::forward<Args>(args)...) {} 193 194 class MOZ_STACK_CLASS Guard : public ExclusiveData<T>::Guard { 195 using Base = typename ExclusiveData<T>::Guard; 196 197 public: Guard(const ExclusiveWaitableData & parent)198 explicit Guard(const ExclusiveWaitableData& parent) : Base(parent) {} 199 Guard(Guard && guard)200 Guard(Guard&& guard) : Base(std::move(guard)) {} 201 202 Guard& operator=(Guard&& rhs) { return Base::operator=(std::move(rhs)); } 203 wait()204 void wait() { 205 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); 206 parent->condVar_.wait(parent->lock_); 207 } 208 notify_one()209 void notify_one() { 210 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); 211 parent->condVar_.notify_one(); 212 } 213 notify_all()214 void notify_all() { 215 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); 216 parent->condVar_.notify_all(); 217 } 218 }; 219 lock()220 Guard lock() const { return Guard(*this); } 221 }; 222 223 } // namespace js 224 225 #endif // threading_ExclusiveData_h 226