Backed out changeset 88fbb17e3c20 (bug 1865637) for causing animation related mochite...
[gecko.git] / js / src / threading / ExclusiveData.h
blob38e89f10a1d3b0731cc2a9bb3a27f6afcf8677ae
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/. */
7 #ifndef threading_ExclusiveData_h
8 #define threading_ExclusiveData_h
10 #include "mozilla/Maybe.h"
11 #include "mozilla/OperatorNewExtensions.h"
13 #include <utility>
15 #include "threading/ConditionVariable.h"
16 #include "threading/Mutex.h"
18 namespace js {
20 /**
21 * [SMDOC] ExclusiveData API
23 * A mutual exclusion lock class.
25 * `ExclusiveData` provides an RAII guard to automatically lock and unlock when
26 * accessing the protected inner value.
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.
33 * For example, suppose we have a counter class:
35 * class Counter
36 * {
37 * int32_t i;
39 * public:
40 * void inc(int32_t n) { i += n; }
41 * };
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:
47 * class SharedCounter
48 * {
49 * // Remember to acquire `counter_lock` when accessing `counter`,
50 * // pretty please!
51 * Counter counter;
52 * std::mutex counter_lock;
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 * };
61 * In contrast, `ExclusiveData` wraps the protected value, enabling the type
62 * system to enforce that we acquire the lock before accessing the value:
64 * class SharedCounter
65 * {
66 * ExclusiveData<Counter> counter;
68 * public:
69 * void inc(size_t n) {
70 * auto guard = counter.lock();
71 * guard->inc(n);
72 * }
73 * };
75 * The API design is based on Rust's `std::sync::Mutex<T>` type.
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!
84 template <typename T>
85 class ExclusiveData {
86 protected:
87 mutable Mutex lock_ MOZ_UNANNOTATED;
88 mutable T value_;
90 ExclusiveData(const ExclusiveData&) = delete;
91 ExclusiveData& operator=(const ExclusiveData&) = delete;
93 void acquire() const { lock_.lock(); }
94 void release() const { lock_.unlock(); }
96 public:
97 /**
98 * Create a new `ExclusiveData`, with perfect forwarding of the protected
99 * value.
101 template <typename U>
102 explicit ExclusiveData(const MutexId& id, U&& u)
103 : lock_(id), value_(std::forward<U>(u)) {}
106 * Create a new `ExclusiveData`, constructing the protected value in place.
108 template <typename... Args>
109 explicit ExclusiveData(const MutexId& id, Args&&... args)
110 : lock_(id), value_(std::forward<Args>(args)...) {}
112 ExclusiveData(ExclusiveData&& rhs)
113 : lock_(std::move(rhs.lock)), value_(std::move(rhs.value_)) {
114 MOZ_ASSERT(&rhs != this, "self-move disallowed!");
117 ExclusiveData& operator=(ExclusiveData&& rhs) {
118 this->~ExclusiveData();
119 new (mozilla::KnownNotNull, this) ExclusiveData(std::move(rhs));
120 return *this;
124 * An RAII class that provides exclusive access to a `ExclusiveData<T>`'s
125 * protected inner `T` value.
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.
131 class MOZ_STACK_CLASS Guard {
132 protected:
133 const ExclusiveData* parent_;
134 explicit Guard(std::nullptr_t) : parent_(nullptr) {}
136 private:
137 Guard(const Guard&) = delete;
138 Guard& operator=(const Guard&) = delete;
140 public:
141 explicit Guard(const ExclusiveData& parent) : parent_(&parent) {
142 parent_->acquire();
145 Guard(Guard&& rhs) : parent_(rhs.parent_) {
146 MOZ_ASSERT(&rhs != this, "self-move disallowed!");
147 rhs.parent_ = nullptr;
150 Guard& operator=(Guard&& rhs) {
151 this->~Guard();
152 new (this) Guard(std::move(rhs));
153 return *this;
156 T& get() const {
157 MOZ_ASSERT(parent_);
158 return parent_->value_;
161 operator T&() const { return get(); }
162 T* operator->() const { return &get(); }
164 const ExclusiveData<T>* parent() const {
165 MOZ_ASSERT(parent_);
166 return parent_;
169 ~Guard() {
170 if (parent_) {
171 parent_->release();
177 * NullableGuard are similar to Guard, except that one the access to the
178 * ExclusiveData might not always be granted. This is useful when contextual
179 * information is enough to prevent useless use of Mutex.
181 * The NullableGuard can be manipulated as follows:
183 * if (NullableGuard guard = data.mightAccess()) {
184 * // NullableGuard is acquired.
185 * guard->...
187 * // NullableGuard was either not acquired or released.
189 * Where mightAccess returns either a NullableGuard from `noAccess()` or a
190 * Guard from `lock()`.
192 class MOZ_STACK_CLASS NullableGuard : public Guard {
193 public:
194 explicit NullableGuard(std::nullptr_t) : Guard((std::nullptr_t) nullptr) {}
195 explicit NullableGuard(const ExclusiveData& parent) : Guard(parent) {}
196 explicit NullableGuard(Guard&& rhs) : Guard(std::move(rhs)) {}
198 NullableGuard& operator=(Guard&& rhs) {
199 this->~NullableGuard();
200 new (this) NullableGuard(std::move(rhs));
201 return *this;
205 * Returns whether this NullableGuard has access to the exclusive data.
207 bool hasAccess() const { return this->parent_; }
208 explicit operator bool() const { return hasAccess(); }
212 * Access the protected inner `T` value for exclusive reading and writing.
214 Guard lock() const { return Guard(*this); }
217 * Provide a no-access guard, which coerces to false when tested. This value
218 * can be returned if the guard access is conditioned on external factors.
220 * See NullableGuard.
222 NullableGuard noAccess() const {
223 return NullableGuard((std::nullptr_t) nullptr);
227 template <class T>
228 class ExclusiveWaitableData : public ExclusiveData<T> {
229 using Base = ExclusiveData<T>;
231 mutable ConditionVariable condVar_;
233 public:
234 template <typename U>
235 explicit ExclusiveWaitableData(const MutexId& id, U&& u)
236 : Base(id, std::forward<U>(u)) {}
238 template <typename... Args>
239 explicit ExclusiveWaitableData(const MutexId& id, Args&&... args)
240 : Base(id, std::forward<Args>(args)...) {}
242 class MOZ_STACK_CLASS Guard : public ExclusiveData<T>::Guard {
243 using Base = typename ExclusiveData<T>::Guard;
245 public:
246 explicit Guard(const ExclusiveWaitableData& parent) : Base(parent) {}
248 Guard(Guard&& guard) : Base(std::move(guard)) {}
250 Guard& operator=(Guard&& rhs) { return Base::operator=(std::move(rhs)); }
252 void wait() {
253 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent());
254 parent->condVar_.wait(parent->lock_);
257 void notify_one() {
258 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent());
259 parent->condVar_.notify_one();
262 void notify_all() {
263 auto* parent = static_cast<const ExclusiveWaitableData*>(this->parent());
264 parent->condVar_.notify_all();
268 Guard lock() const { return Guard(*this); }
272 * Multiple-readers / single-writer variant of ExclusiveData.
274 * Readers call readLock() to obtain a stack-only RAII reader lock, which will
275 * allow other readers to read concurrently but block writers; the yielded value
276 * is const. Writers call writeLock() to obtain a ditto writer lock, which
277 * yields exclusive access to non-const data.
279 * See ExclusiveData and its implementation for more documentation.
281 template <typename T>
282 class RWExclusiveData {
283 mutable Mutex lock_ MOZ_UNANNOTATED;
284 mutable ConditionVariable cond_;
285 mutable T value_;
286 mutable int readers_;
288 // We maintain a count of active readers. Writers may enter the critical
289 // section only when the reader count is zero, so the reader that decrements
290 // the count to zero must wake up any waiting writers.
292 // There can be multiple writers waiting, so a writer leaving the critical
293 // section must also wake up any other waiting writers.
295 void acquireReaderLock() const {
296 lock_.lock();
297 readers_++;
298 lock_.unlock();
301 void releaseReaderLock() const {
302 lock_.lock();
303 MOZ_ASSERT(readers_ > 0);
304 if (--readers_ == 0) {
305 cond_.notify_all();
307 lock_.unlock();
310 void acquireWriterLock() const {
311 lock_.lock();
312 while (readers_ > 0) {
313 cond_.wait(lock_);
317 void releaseWriterLock() const {
318 cond_.notify_all();
319 lock_.unlock();
322 public:
323 RWExclusiveData(const RWExclusiveData&) = delete;
324 RWExclusiveData& operator=(const RWExclusiveData&) = delete;
327 * Create a new `RWExclusiveData`, constructing the protected value in place.
329 template <typename... Args>
330 explicit RWExclusiveData(const MutexId& id, Args&&... args)
331 : lock_(id), value_(std::forward<Args>(args)...), readers_(0) {}
333 class MOZ_STACK_CLASS ReadGuard {
334 const RWExclusiveData* parent_;
335 explicit ReadGuard(std::nullptr_t) : parent_(nullptr) {}
337 public:
338 ReadGuard(const ReadGuard&) = delete;
339 ReadGuard& operator=(const ReadGuard&) = delete;
341 explicit ReadGuard(const RWExclusiveData& parent) : parent_(&parent) {
342 parent_->acquireReaderLock();
345 ReadGuard(ReadGuard&& rhs) : parent_(rhs.parent_) {
346 MOZ_ASSERT(&rhs != this, "self-move disallowed!");
347 rhs.parent_ = nullptr;
350 ReadGuard& operator=(ReadGuard&& rhs) {
351 this->~ReadGuard();
352 new (this) ReadGuard(std::move(rhs));
353 return *this;
356 const T& get() const {
357 MOZ_ASSERT(parent_);
358 return parent_->value_;
361 operator const T&() const { return get(); }
362 const T* operator->() const { return &get(); }
364 const RWExclusiveData<T>* parent() const {
365 MOZ_ASSERT(parent_);
366 return parent_;
369 ~ReadGuard() {
370 if (parent_) {
371 parent_->releaseReaderLock();
376 class MOZ_STACK_CLASS WriteGuard {
377 const RWExclusiveData* parent_;
378 explicit WriteGuard(std::nullptr_t) : parent_(nullptr) {}
380 public:
381 WriteGuard(const WriteGuard&) = delete;
382 WriteGuard& operator=(const WriteGuard&) = delete;
384 explicit WriteGuard(const RWExclusiveData& parent) : parent_(&parent) {
385 parent_->acquireWriterLock();
388 WriteGuard(WriteGuard&& rhs) : parent_(rhs.parent_) {
389 MOZ_ASSERT(&rhs != this, "self-move disallowed!");
390 rhs.parent_ = nullptr;
393 WriteGuard& operator=(WriteGuard&& rhs) {
394 this->~WriteGuard();
395 new (this) WriteGuard(std::move(rhs));
396 return *this;
399 T& get() const {
400 MOZ_ASSERT(parent_);
401 return parent_->value_;
404 operator T&() const { return get(); }
405 T* operator->() const { return &get(); }
407 const RWExclusiveData<T>* parent() const {
408 MOZ_ASSERT(parent_);
409 return parent_;
412 ~WriteGuard() {
413 if (parent_) {
414 parent_->releaseWriterLock();
419 ReadGuard readLock() const { return ReadGuard(*this); }
420 WriteGuard writeLock() const { return WriteGuard(*this); }
423 } // namespace js
425 #endif // threading_ExclusiveData_h