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