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