ExclusiveData.h (11828B)
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/OperatorNewExtensions.h" 11 12 #include <utility> 13 14 #include "threading/ConditionVariable.h" 15 #include "threading/Mutex.h" 16 17 namespace js { 18 19 /** 20 * [SMDOC] ExclusiveData API 21 * 22 * A mutual exclusion lock class. 23 * 24 * `ExclusiveData` provides an RAII guard to automatically lock and unlock when 25 * accessing the protected inner value. 26 * 27 * Unlike the STL's `std::mutex`, the protected value is internal to this 28 * class. This is a huge win: one no longer has to rely on documentation to 29 * explain the relationship between a lock and its protected data, and the type 30 * system can enforce[0] it. 31 * 32 * For example, suppose we have a counter class: 33 * 34 * class Counter 35 * { 36 * int32_t i; 37 * 38 * public: 39 * void inc(int32_t n) { i += n; } 40 * }; 41 * 42 * If we share a counter across threads with `std::mutex`, we rely solely on 43 * comments to document the relationship between the lock and its data, like 44 * this: 45 * 46 * class SharedCounter 47 * { 48 * // Remember to acquire `counter_lock` when accessing `counter`, 49 * // pretty please! 50 * Counter counter; 51 * std::mutex counter_lock; 52 * 53 * public: 54 * void inc(size_t n) { 55 * // Whoops, forgot to acquire the lock! Off to the races! 56 * counter.inc(n); 57 * } 58 * }; 59 * 60 * In contrast, `ExclusiveData` wraps the protected value, enabling the type 61 * system to enforce that we acquire the lock before accessing the value: 62 * 63 * class SharedCounter 64 * { 65 * ExclusiveData<Counter> counter; 66 * 67 * public: 68 * void inc(size_t n) { 69 * auto guard = counter.lock(); 70 * guard->inc(n); 71 * } 72 * }; 73 * 74 * The API design is based on Rust's `std::sync::Mutex<T>` type. 75 * 76 * [0]: Of course, we don't have a borrow checker in C++, so the type system 77 * cannot guarantee that you don't stash references received from 78 * `ExclusiveData<T>::Guard` somewhere such that the reference outlives the 79 * guard's lifetime and therefore becomes invalid. To help avoid this last 80 * foot-gun, prefer using the guard directly! Do not store raw references 81 * to the protected value in other structures! 82 */ 83 template <typename T> 84 class ExclusiveData { 85 protected: 86 mutable Mutex lock_ MOZ_UNANNOTATED; 87 mutable T value_; 88 89 ExclusiveData(const ExclusiveData&) = delete; 90 ExclusiveData& operator=(const ExclusiveData&) = delete; 91 92 void acquire() const { lock_.lock(); } 93 void release() const { lock_.unlock(); } 94 95 public: 96 /** 97 * Create a new `ExclusiveData`, with perfect forwarding of the protected 98 * value. 99 */ 100 template <typename U> 101 explicit ExclusiveData(const MutexId& id, U&& u) 102 : lock_(id), value_(std::forward<U>(u)) {} 103 104 /** 105 * Create a new `ExclusiveData`, constructing the protected value in place. 106 */ 107 template <typename... Args> 108 explicit ExclusiveData(const MutexId& id, Args&&... args) 109 : lock_(id), value_(std::forward<Args>(args)...) {} 110 111 ExclusiveData& operator=(ExclusiveData&& rhs) { 112 this->~ExclusiveData(); 113 new (mozilla::KnownNotNull, this) ExclusiveData(std::move(rhs)); 114 return *this; 115 } 116 117 /** 118 * An RAII class that provides exclusive access to a `ExclusiveData<T>`'s 119 * protected inner `T` value. 120 * 121 * Note that this is intentionally marked MOZ_STACK_CLASS instead of 122 * MOZ_RAII_CLASS, as the latter disallows moves and returning by value, but 123 * Guard utilizes both. 124 */ 125 class MOZ_STACK_CLASS Guard { 126 protected: 127 const ExclusiveData* parent_; 128 explicit Guard(std::nullptr_t) : parent_(nullptr) {} 129 130 private: 131 Guard(const Guard&) = delete; 132 Guard& operator=(const Guard&) = delete; 133 134 public: 135 explicit Guard(const ExclusiveData& parent) : parent_(&parent) { 136 parent_->acquire(); 137 } 138 139 Guard(Guard&& rhs) : parent_(rhs.parent_) { 140 MOZ_ASSERT(&rhs != this, "self-move disallowed!"); 141 rhs.parent_ = nullptr; 142 } 143 144 Guard& operator=(Guard&& rhs) { 145 this->~Guard(); 146 new (this) Guard(std::move(rhs)); 147 return *this; 148 } 149 150 T& get() const { 151 MOZ_ASSERT(parent_); 152 return parent_->value_; 153 } 154 155 operator T&() const { return get(); } 156 T* operator->() const { return &get(); } 157 158 const ExclusiveData<T>* parent() const { 159 MOZ_ASSERT(parent_); 160 return parent_; 161 } 162 163 ~Guard() { 164 if (parent_) { 165 parent_->release(); 166 } 167 } 168 }; 169 170 /** 171 * NullableGuard are similar to Guard, except that one the access to the 172 * ExclusiveData might not always be granted. This is useful when contextual 173 * information is enough to prevent useless use of Mutex. 174 * 175 * The NullableGuard can be manipulated as follows: 176 * 177 * if (NullableGuard guard = data.mightAccess()) { 178 * // NullableGuard is acquired. 179 * guard->... 180 * } 181 * // NullableGuard was either not acquired or released. 182 * 183 * Where mightAccess returns either a NullableGuard from `noAccess()` or a 184 * Guard from `lock()`. 185 */ 186 class MOZ_STACK_CLASS NullableGuard : public Guard { 187 public: 188 explicit NullableGuard(std::nullptr_t) : Guard((std::nullptr_t) nullptr) {} 189 explicit NullableGuard(const ExclusiveData& parent) : Guard(parent) {} 190 explicit NullableGuard(Guard&& rhs) : Guard(std::move(rhs)) {} 191 192 NullableGuard& operator=(Guard&& rhs) { 193 this->~NullableGuard(); 194 new (this) NullableGuard(std::move(rhs)); 195 return *this; 196 } 197 198 /** 199 * Returns whether this NullableGuard has access to the exclusive data. 200 */ 201 bool hasAccess() const { return this->parent_; } 202 explicit operator bool() const { return hasAccess(); } 203 }; 204 205 /** 206 * Access the protected inner `T` value for exclusive reading and writing. 207 */ 208 Guard lock() const { return Guard(*this); } 209 210 /** 211 * Provide a no-access guard, which coerces to false when tested. This value 212 * can be returned if the guard access is conditioned on external factors. 213 * 214 * See NullableGuard. 215 */ 216 NullableGuard noAccess() const { 217 return NullableGuard((std::nullptr_t) nullptr); 218 } 219 }; 220 221 template <class T> 222 class ExclusiveWaitableData : public ExclusiveData<T> { 223 using Base = ExclusiveData<T>; 224 225 mutable ConditionVariable condVar_; 226 227 public: 228 template <typename U> 229 explicit ExclusiveWaitableData(const MutexId& id, U&& u) 230 : Base(id, std::forward<U>(u)) {} 231 232 template <typename... Args> 233 explicit ExclusiveWaitableData(const MutexId& id, Args&&... args) 234 : Base(id, std::forward<Args>(args)...) {} 235 236 class MOZ_STACK_CLASS Guard : public ExclusiveData<T>::Guard { 237 using Base = typename ExclusiveData<T>::Guard; 238 239 public: 240 explicit Guard(const ExclusiveWaitableData& parent) : Base(parent) {} 241 242 Guard(Guard&& guard) : Base(std::move(guard)) {} 243 244 Guard& operator=(Guard&& rhs) { return Base::operator=(std::move(rhs)); } 245 246 void wait() { 247 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); 248 parent->condVar_.wait(parent->lock_); 249 } 250 251 void notify_one() { 252 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); 253 parent->condVar_.notify_one(); 254 } 255 256 void notify_all() { 257 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent()); 258 parent->condVar_.notify_all(); 259 } 260 }; 261 262 Guard lock() const { return Guard(*this); } 263 }; 264 265 /** 266 * Multiple-readers / single-writer variant of ExclusiveData. 267 * 268 * Readers call readLock() to obtain a stack-only RAII reader lock, which will 269 * allow other readers to read concurrently but block writers; the yielded value 270 * is const. Writers call writeLock() to obtain a ditto writer lock, which 271 * yields exclusive access to non-const data. 272 * 273 * See ExclusiveData and its implementation for more documentation. 274 */ 275 template <typename T> 276 class RWExclusiveData { 277 mutable Mutex lock_ MOZ_UNANNOTATED; 278 mutable ConditionVariable cond_; 279 mutable T value_; 280 mutable int readers_; 281 282 // We maintain a count of active readers. Writers may enter the critical 283 // section only when the reader count is zero, so the reader that decrements 284 // the count to zero must wake up any waiting writers. 285 // 286 // There can be multiple writers waiting, so a writer leaving the critical 287 // section must also wake up any other waiting writers. 288 289 void acquireReaderLock() const { 290 lock_.lock(); 291 readers_++; 292 lock_.unlock(); 293 } 294 295 void releaseReaderLock() const { 296 lock_.lock(); 297 MOZ_ASSERT(readers_ > 0); 298 if (--readers_ == 0) { 299 cond_.notify_all(); 300 } 301 lock_.unlock(); 302 } 303 304 void acquireWriterLock() const { 305 lock_.lock(); 306 while (readers_ > 0) { 307 cond_.wait(lock_); 308 } 309 } 310 311 void releaseWriterLock() const { 312 cond_.notify_all(); 313 lock_.unlock(); 314 } 315 316 public: 317 RWExclusiveData(const RWExclusiveData&) = delete; 318 RWExclusiveData& operator=(const RWExclusiveData&) = delete; 319 320 /** 321 * Create a new `RWExclusiveData`, constructing the protected value in place. 322 */ 323 template <typename... Args> 324 explicit RWExclusiveData(const MutexId& id, Args&&... args) 325 : lock_(id), value_(std::forward<Args>(args)...), readers_(0) {} 326 327 class MOZ_STACK_CLASS ReadGuard { 328 const RWExclusiveData* parent_; 329 explicit ReadGuard(std::nullptr_t) : parent_(nullptr) {} 330 331 public: 332 ReadGuard(const ReadGuard&) = delete; 333 ReadGuard& operator=(const ReadGuard&) = delete; 334 335 explicit ReadGuard(const RWExclusiveData& parent) : parent_(&parent) { 336 parent_->acquireReaderLock(); 337 } 338 339 ReadGuard(ReadGuard&& rhs) : parent_(rhs.parent_) { 340 MOZ_ASSERT(&rhs != this, "self-move disallowed!"); 341 rhs.parent_ = nullptr; 342 } 343 344 ReadGuard& operator=(ReadGuard&& rhs) { 345 this->~ReadGuard(); 346 new (this) ReadGuard(std::move(rhs)); 347 return *this; 348 } 349 350 const T& get() const { 351 MOZ_ASSERT(parent_); 352 return parent_->value_; 353 } 354 355 operator const T&() const { return get(); } 356 const T* operator->() const { return &get(); } 357 358 const RWExclusiveData<T>* parent() const { 359 MOZ_ASSERT(parent_); 360 return parent_; 361 } 362 363 ~ReadGuard() { 364 if (parent_) { 365 parent_->releaseReaderLock(); 366 } 367 } 368 }; 369 370 class MOZ_STACK_CLASS WriteGuard { 371 const RWExclusiveData* parent_; 372 explicit WriteGuard(std::nullptr_t) : parent_(nullptr) {} 373 374 public: 375 WriteGuard(const WriteGuard&) = delete; 376 WriteGuard& operator=(const WriteGuard&) = delete; 377 378 explicit WriteGuard(const RWExclusiveData& parent) : parent_(&parent) { 379 parent_->acquireWriterLock(); 380 } 381 382 WriteGuard(WriteGuard&& rhs) : parent_(rhs.parent_) { 383 MOZ_ASSERT(&rhs != this, "self-move disallowed!"); 384 rhs.parent_ = nullptr; 385 } 386 387 WriteGuard& operator=(WriteGuard&& rhs) { 388 this->~WriteGuard(); 389 new (this) WriteGuard(std::move(rhs)); 390 return *this; 391 } 392 393 T& get() const { 394 MOZ_ASSERT(parent_); 395 return parent_->value_; 396 } 397 398 operator T&() const { return get(); } 399 T* operator->() const { return &get(); } 400 401 const RWExclusiveData<T>* parent() const { 402 MOZ_ASSERT(parent_); 403 return parent_; 404 } 405 406 ~WriteGuard() { 407 if (parent_) { 408 parent_->releaseWriterLock(); 409 } 410 } 411 }; 412 413 ReadGuard readLock() const { return ReadGuard(*this); } 414 WriteGuard writeLock() const { return WriteGuard(*this); } 415 }; 416 417 } // namespace js 418 419 #endif // threading_ExclusiveData_h