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