Line data Source code
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 2368 : void acquire() const { lock_.lock(); }
90 2368 : 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 0 : explicit ExclusiveData(const MutexId& id, U&& u)
99 0 : : lock_(id)
100 : {
101 0 : new (value_.addr()) T(mozilla::Forward<U>(u));
102 0 : }
103 :
104 : /**
105 : * Create a new `ExclusiveData`, constructing the protected value in place.
106 : */
107 : template <typename... Args>
108 60 : explicit ExclusiveData(const MutexId& id, Args&&... args)
109 60 : : lock_(id)
110 : {
111 60 : new (value_.addr()) T(mozilla::Forward<Args>(args)...);
112 60 : }
113 :
114 0 : ~ExclusiveData() {
115 0 : acquire();
116 0 : value_.addr()->~T();
117 0 : release();
118 0 : }
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 2368 : explicit Guard(const ExclusiveData& parent)
150 2368 : : parent_(&parent)
151 : {
152 2368 : parent_->acquire();
153 2368 : }
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 7022 : T& get() const {
169 7022 : MOZ_ASSERT(parent_);
170 7022 : return *parent_->value_.addr();
171 : }
172 :
173 0 : operator T& () const { return get(); }
174 6700 : T* operator->() const { return &get(); }
175 :
176 1704 : const ExclusiveData<T>* parent() const {
177 1704 : MOZ_ASSERT(parent_);
178 1704 : return parent_;
179 : }
180 :
181 2368 : ~Guard() {
182 2368 : if (parent_)
183 2368 : parent_->release();
184 2368 : }
185 : };
186 :
187 : /**
188 : * Access the protected inner `T` value for exclusive reading and writing.
189 : */
190 2368 : Guard lock() const {
191 2368 : return Guard(*this);
192 : }
193 : };
194 :
195 : } // namespace js
196 :
197 : #endif // threading_ExclusiveData_h
|