jpayne@69
|
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors
|
jpayne@69
|
2 // Licensed under the MIT License:
|
jpayne@69
|
3 //
|
jpayne@69
|
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
|
jpayne@69
|
5 // of this software and associated documentation files (the "Software"), to deal
|
jpayne@69
|
6 // in the Software without restriction, including without limitation the rights
|
jpayne@69
|
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
jpayne@69
|
8 // copies of the Software, and to permit persons to whom the Software is
|
jpayne@69
|
9 // furnished to do so, subject to the following conditions:
|
jpayne@69
|
10 //
|
jpayne@69
|
11 // The above copyright notice and this permission notice shall be included in
|
jpayne@69
|
12 // all copies or substantial portions of the Software.
|
jpayne@69
|
13 //
|
jpayne@69
|
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
jpayne@69
|
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
jpayne@69
|
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
jpayne@69
|
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
jpayne@69
|
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
jpayne@69
|
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
jpayne@69
|
20 // THE SOFTWARE.
|
jpayne@69
|
21
|
jpayne@69
|
22 #pragma once
|
jpayne@69
|
23
|
jpayne@69
|
24 #include "debug.h"
|
jpayne@69
|
25 #include "memory.h"
|
jpayne@69
|
26 #include <inttypes.h>
|
jpayne@69
|
27 #include "time.h"
|
jpayne@69
|
28 #include "source-location.h"
|
jpayne@69
|
29 #include "one-of.h"
|
jpayne@69
|
30
|
jpayne@69
|
31 KJ_BEGIN_HEADER
|
jpayne@69
|
32
|
jpayne@69
|
33 #if __linux__ && !defined(KJ_USE_FUTEX)
|
jpayne@69
|
34 #define KJ_USE_FUTEX 1
|
jpayne@69
|
35 #endif
|
jpayne@69
|
36
|
jpayne@69
|
37 #if !KJ_USE_FUTEX && !_WIN32 && !__CYGWIN__
|
jpayne@69
|
38 // We fall back to pthreads when we don't have a better platform-specific primitive. pthreads
|
jpayne@69
|
39 // mutexes are bloated, though, so we like to avoid them. Hence on Linux we use futex(), and on
|
jpayne@69
|
40 // Windows we use SRW locks and friends. On Cygwin we prefer the Win32 primitives both because they
|
jpayne@69
|
41 // are more efficient and because I ran into problems with Cygwin's implementation of RW locks
|
jpayne@69
|
42 // seeming to allow multiple threads to lock the same mutex (but I didn't investigate very
|
jpayne@69
|
43 // closely).
|
jpayne@69
|
44 //
|
jpayne@69
|
45 // TODO(someday): Write efficient low-level locking primitives for other platforms.
|
jpayne@69
|
46 #include <pthread.h>
|
jpayne@69
|
47 #endif
|
jpayne@69
|
48
|
jpayne@69
|
49 // There are 3 macros controlling lock tracking:
|
jpayne@69
|
50 // KJ_TRACK_LOCK_BLOCKING will set up async signal safe TLS variables that can be used to identify
|
jpayne@69
|
51 // the KJ primitive blocking the current thread.
|
jpayne@69
|
52 // KJ_SAVE_ACQUIRED_LOCK_INFO will allow introspection of a Mutex to get information about what is
|
jpayne@69
|
53 // currently holding the lock.
|
jpayne@69
|
54 // KJ_TRACK_LOCK_ACQUISITION is automatically enabled by either one of them.
|
jpayne@69
|
55
|
jpayne@69
|
56 #if KJ_TRACK_LOCK_BLOCKING
|
jpayne@69
|
57 // Lock tracking is required to keep track of what blocked.
|
jpayne@69
|
58 #define KJ_TRACK_LOCK_ACQUISITION 1
|
jpayne@69
|
59 #endif
|
jpayne@69
|
60
|
jpayne@69
|
61 #if KJ_SAVE_ACQUIRED_LOCK_INFO
|
jpayne@69
|
62 #define KJ_TRACK_LOCK_ACQUISITION 1
|
jpayne@69
|
63 #include <unistd.h>
|
jpayne@69
|
64 #endif
|
jpayne@69
|
65
|
jpayne@69
|
66 namespace kj {
|
jpayne@69
|
67 #if KJ_TRACK_LOCK_ACQUISITION
|
jpayne@69
|
68 #if !KJ_USE_FUTEX
|
jpayne@69
|
69 #error Lock tracking is only currently supported for futex-based mutexes.
|
jpayne@69
|
70 #endif
|
jpayne@69
|
71
|
jpayne@69
|
72 #if !KJ_COMPILER_SUPPORTS_SOURCE_LOCATION
|
jpayne@69
|
73 #error C++20 or newer is required (or the use of clang/gcc).
|
jpayne@69
|
74 #endif
|
jpayne@69
|
75
|
jpayne@69
|
76 using LockSourceLocation = SourceLocation;
|
jpayne@69
|
77 using LockSourceLocationArg = const SourceLocation&;
|
jpayne@69
|
78 // On x86-64 the codegen is optimal if the argument has type const& for the location. However,
|
jpayne@69
|
79 // since this conflicts with the optimal call signature for NoopSourceLocation,
|
jpayne@69
|
80 // LockSourceLocationArg is used to conditionally select the right type without polluting the usage
|
jpayne@69
|
81 // themselves. Interestingly this makes no difference on ARM.
|
jpayne@69
|
82 // https://godbolt.org/z/q6G8ee5a3
|
jpayne@69
|
83 #else
|
jpayne@69
|
84 using LockSourceLocation = NoopSourceLocation;
|
jpayne@69
|
85 using LockSourceLocationArg = NoopSourceLocation;
|
jpayne@69
|
86 #endif
|
jpayne@69
|
87
|
jpayne@69
|
88
|
jpayne@69
|
89 class Exception;
|
jpayne@69
|
90
|
jpayne@69
|
91 // =======================================================================================
|
jpayne@69
|
92 // Private details -- public interfaces follow below.
|
jpayne@69
|
93
|
jpayne@69
|
94 namespace _ { // private
|
jpayne@69
|
95
|
jpayne@69
|
96 #if KJ_SAVE_ACQUIRED_LOCK_INFO
|
jpayne@69
|
97 class HoldingExclusively {
|
jpayne@69
|
98 // The lock is being held in exclusive mode.
|
jpayne@69
|
99 public:
|
jpayne@69
|
100 constexpr HoldingExclusively(pid_t tid, const SourceLocation& location)
|
jpayne@69
|
101 : heldBy(tid), acquiredAt(location) {}
|
jpayne@69
|
102
|
jpayne@69
|
103 pid_t threadHoldingLock() const { return heldBy; }
|
jpayne@69
|
104 const SourceLocation& lockAcquiredAt() const { return acquiredAt; }
|
jpayne@69
|
105
|
jpayne@69
|
106 private:
|
jpayne@69
|
107 pid_t heldBy;
|
jpayne@69
|
108 SourceLocation acquiredAt;
|
jpayne@69
|
109 };
|
jpayne@69
|
110
|
jpayne@69
|
111 class HoldingShared {
|
jpayne@69
|
112 // The lock is being held in shared mode currently. Which threads are holding this lock open
|
jpayne@69
|
113 // is unknown.
|
jpayne@69
|
114 public:
|
jpayne@69
|
115 constexpr HoldingShared(const SourceLocation& location) : acquiredAt(location) {}
|
jpayne@69
|
116
|
jpayne@69
|
117 const SourceLocation& lockAcquiredAt() const { return acquiredAt; }
|
jpayne@69
|
118
|
jpayne@69
|
119 private:
|
jpayne@69
|
120 SourceLocation acquiredAt;
|
jpayne@69
|
121 };
|
jpayne@69
|
122 #endif
|
jpayne@69
|
123
|
jpayne@69
|
124 class Mutex {
|
jpayne@69
|
125 // Internal implementation details. See `MutexGuarded<T>`.
|
jpayne@69
|
126
|
jpayne@69
|
127 struct Waiter;
|
jpayne@69
|
128
|
jpayne@69
|
129 public:
|
jpayne@69
|
130 Mutex();
|
jpayne@69
|
131 ~Mutex();
|
jpayne@69
|
132 KJ_DISALLOW_COPY_AND_MOVE(Mutex);
|
jpayne@69
|
133
|
jpayne@69
|
134 enum Exclusivity {
|
jpayne@69
|
135 EXCLUSIVE,
|
jpayne@69
|
136 SHARED
|
jpayne@69
|
137 };
|
jpayne@69
|
138
|
jpayne@69
|
139 bool lock(Exclusivity exclusivity, Maybe<Duration> timeout, LockSourceLocationArg location);
|
jpayne@69
|
140 void unlock(Exclusivity exclusivity, Waiter* waiterToSkip = nullptr);
|
jpayne@69
|
141
|
jpayne@69
|
142 void assertLockedByCaller(Exclusivity exclusivity) const;
|
jpayne@69
|
143 // In debug mode, assert that the mutex is locked by the calling thread, or if that is
|
jpayne@69
|
144 // non-trivial, assert that the mutex is locked (which should be good enough to catch problems
|
jpayne@69
|
145 // in unit tests). In non-debug builds, do nothing.
|
jpayne@69
|
146
|
jpayne@69
|
147 class Predicate {
|
jpayne@69
|
148 public:
|
jpayne@69
|
149 virtual bool check() = 0;
|
jpayne@69
|
150 };
|
jpayne@69
|
151
|
jpayne@69
|
152 void wait(Predicate& predicate, Maybe<Duration> timeout, LockSourceLocationArg location);
|
jpayne@69
|
153 // If predicate.check() returns false, unlock the mutex until predicate.check() returns true, or
|
jpayne@69
|
154 // when the timeout (if any) expires. The mutex is always re-locked when this returns regardless
|
jpayne@69
|
155 // of whether the timeout expired, and including if it throws.
|
jpayne@69
|
156 //
|
jpayne@69
|
157 // Requires that the mutex is already exclusively locked before calling.
|
jpayne@69
|
158
|
jpayne@69
|
159 void induceSpuriousWakeupForTest();
|
jpayne@69
|
160 // Utility method for mutex-test.c++ which causes a spurious thread wakeup on all threads that
|
jpayne@69
|
161 // are waiting for a wait() condition. Assuming correct implementation, all those threads
|
jpayne@69
|
162 // should immediately go back to sleep.
|
jpayne@69
|
163
|
jpayne@69
|
164 #if KJ_USE_FUTEX
|
jpayne@69
|
165 uint numReadersWaitingForTest() const;
|
jpayne@69
|
166 // The number of reader locks that are currently blocked on this lock (must be called while
|
jpayne@69
|
167 // holding the writer lock). This is really only a utility method for mutex-test.c++ so it can
|
jpayne@69
|
168 // validate certain invariants.
|
jpayne@69
|
169 #endif
|
jpayne@69
|
170
|
jpayne@69
|
171 #if KJ_SAVE_ACQUIRED_LOCK_INFO
|
jpayne@69
|
172 using AcquiredMetadata = kj::OneOf<HoldingExclusively, HoldingShared>;
|
jpayne@69
|
173 KJ_DISABLE_TSAN AcquiredMetadata lockedInfo() const;
|
jpayne@69
|
174 // Returns metadata about this lock when its held. This method is async signal safe. It must also
|
jpayne@69
|
175 // be called in a state where it's guaranteed that the lock state won't be released by another
|
jpayne@69
|
176 // thread. In other words this has to be called from the signal handler within the thread that's
|
jpayne@69
|
177 // holding the lock.
|
jpayne@69
|
178 #endif
|
jpayne@69
|
179
|
jpayne@69
|
180 private:
|
jpayne@69
|
181 #if KJ_USE_FUTEX
|
jpayne@69
|
182 uint futex;
|
jpayne@69
|
183 // bit 31 (msb) = set if exclusive lock held
|
jpayne@69
|
184 // bit 30 (msb) = set if threads are waiting for exclusive lock
|
jpayne@69
|
185 // bits 0-29 = count of readers; If an exclusive lock is held, this is the count of threads
|
jpayne@69
|
186 // waiting for a read lock, otherwise it is the count of threads that currently hold a read
|
jpayne@69
|
187 // lock.
|
jpayne@69
|
188
|
jpayne@69
|
189 #ifdef KJ_CONTENTION_WARNING_THRESHOLD
|
jpayne@69
|
190 bool printContendedReader = false;
|
jpayne@69
|
191 #endif
|
jpayne@69
|
192
|
jpayne@69
|
193 static constexpr uint EXCLUSIVE_HELD = 1u << 31;
|
jpayne@69
|
194 static constexpr uint EXCLUSIVE_REQUESTED = 1u << 30;
|
jpayne@69
|
195 static constexpr uint SHARED_COUNT_MASK = EXCLUSIVE_REQUESTED - 1;
|
jpayne@69
|
196
|
jpayne@69
|
197 #elif _WIN32 || __CYGWIN__
|
jpayne@69
|
198 uintptr_t srwLock; // Actually an SRWLOCK, but don't want to #include <windows.h> in header.
|
jpayne@69
|
199
|
jpayne@69
|
200 #else
|
jpayne@69
|
201 mutable pthread_rwlock_t mutex;
|
jpayne@69
|
202 #endif
|
jpayne@69
|
203
|
jpayne@69
|
204 #if KJ_SAVE_ACQUIRED_LOCK_INFO
|
jpayne@69
|
205 pid_t lockedExclusivelyByThread = 0;
|
jpayne@69
|
206 SourceLocation lockAcquiredLocation;
|
jpayne@69
|
207
|
jpayne@69
|
208 KJ_DISABLE_TSAN void acquiredExclusive(pid_t tid, const SourceLocation& location) noexcept {
|
jpayne@69
|
209 lockAcquiredLocation = location;
|
jpayne@69
|
210 __atomic_store_n(&lockedExclusivelyByThread, tid, __ATOMIC_RELAXED);
|
jpayne@69
|
211 }
|
jpayne@69
|
212
|
jpayne@69
|
213 KJ_DISABLE_TSAN void acquiredShared(const SourceLocation& location) noexcept {
|
jpayne@69
|
214 lockAcquiredLocation = location;
|
jpayne@69
|
215 }
|
jpayne@69
|
216
|
jpayne@69
|
217 KJ_DISABLE_TSAN SourceLocation releasingExclusive() noexcept {
|
jpayne@69
|
218 auto tmp = lockAcquiredLocation;
|
jpayne@69
|
219 lockAcquiredLocation = SourceLocation{};
|
jpayne@69
|
220 lockedExclusivelyByThread = 0;
|
jpayne@69
|
221 return tmp;
|
jpayne@69
|
222 }
|
jpayne@69
|
223 #else
|
jpayne@69
|
224 static constexpr void acquiredExclusive(uint, LockSourceLocationArg) {}
|
jpayne@69
|
225 static constexpr void acquiredShared(LockSourceLocationArg) {}
|
jpayne@69
|
226 static constexpr NoopSourceLocation releasingExclusive() { return NoopSourceLocation{}; }
|
jpayne@69
|
227 #endif
|
jpayne@69
|
228 struct Waiter {
|
jpayne@69
|
229 kj::Maybe<Waiter&> next;
|
jpayne@69
|
230 kj::Maybe<Waiter&>* prev;
|
jpayne@69
|
231 Predicate& predicate;
|
jpayne@69
|
232 Maybe<Own<Exception>> exception;
|
jpayne@69
|
233 #if KJ_USE_FUTEX
|
jpayne@69
|
234 uint futex;
|
jpayne@69
|
235 bool hasTimeout;
|
jpayne@69
|
236 #elif _WIN32 || __CYGWIN__
|
jpayne@69
|
237 uintptr_t condvar;
|
jpayne@69
|
238 // Actually CONDITION_VARIABLE, but don't want to #include <windows.h> in header.
|
jpayne@69
|
239 #else
|
jpayne@69
|
240 pthread_cond_t condvar;
|
jpayne@69
|
241
|
jpayne@69
|
242 pthread_mutex_t stupidMutex;
|
jpayne@69
|
243 // pthread condvars are only compatible with basic pthread mutexes, not rwlocks, for no
|
jpayne@69
|
244 // particularly good reason. To work around this, we need an extra mutex per condvar.
|
jpayne@69
|
245 #endif
|
jpayne@69
|
246 };
|
jpayne@69
|
247
|
jpayne@69
|
248 kj::Maybe<Waiter&> waitersHead = nullptr;
|
jpayne@69
|
249 kj::Maybe<Waiter&>* waitersTail = &waitersHead;
|
jpayne@69
|
250 // linked list of waiters; can only modify under lock
|
jpayne@69
|
251
|
jpayne@69
|
252 inline void addWaiter(Waiter& waiter);
|
jpayne@69
|
253 inline void removeWaiter(Waiter& waiter);
|
jpayne@69
|
254 bool checkPredicate(Waiter& waiter);
|
jpayne@69
|
255 #if _WIN32 || __CYGWIN__
|
jpayne@69
|
256 void wakeReadyWaiter(Waiter* waiterToSkip);
|
jpayne@69
|
257 #endif
|
jpayne@69
|
258 };
|
jpayne@69
|
259
|
jpayne@69
|
260 class Once {
|
jpayne@69
|
261 // Internal implementation details. See `Lazy<T>`.
|
jpayne@69
|
262
|
jpayne@69
|
263 public:
|
jpayne@69
|
264 #if KJ_USE_FUTEX
|
jpayne@69
|
265 inline Once(bool startInitialized = false)
|
jpayne@69
|
266 : futex(startInitialized ? INITIALIZED : UNINITIALIZED) {}
|
jpayne@69
|
267 #else
|
jpayne@69
|
268 Once(bool startInitialized = false);
|
jpayne@69
|
269 ~Once();
|
jpayne@69
|
270 #endif
|
jpayne@69
|
271 KJ_DISALLOW_COPY_AND_MOVE(Once);
|
jpayne@69
|
272
|
jpayne@69
|
273 class Initializer {
|
jpayne@69
|
274 public:
|
jpayne@69
|
275 virtual void run() = 0;
|
jpayne@69
|
276 };
|
jpayne@69
|
277
|
jpayne@69
|
278 void runOnce(Initializer& init, LockSourceLocationArg location);
|
jpayne@69
|
279
|
jpayne@69
|
280 #if _WIN32 || __CYGWIN__ // TODO(perf): Can we make this inline on win32 somehow?
|
jpayne@69
|
281 bool isInitialized() noexcept;
|
jpayne@69
|
282
|
jpayne@69
|
283 #else
|
jpayne@69
|
284 inline bool isInitialized() noexcept {
|
jpayne@69
|
285 // Fast path check to see if runOnce() would simply return immediately.
|
jpayne@69
|
286 #if KJ_USE_FUTEX
|
jpayne@69
|
287 return __atomic_load_n(&futex, __ATOMIC_ACQUIRE) == INITIALIZED;
|
jpayne@69
|
288 #else
|
jpayne@69
|
289 return __atomic_load_n(&state, __ATOMIC_ACQUIRE) == INITIALIZED;
|
jpayne@69
|
290 #endif
|
jpayne@69
|
291 }
|
jpayne@69
|
292 #endif
|
jpayne@69
|
293
|
jpayne@69
|
294 void reset();
|
jpayne@69
|
295 // Returns the state from initialized to uninitialized. It is an error to call this when
|
jpayne@69
|
296 // not already initialized, or when runOnce() or isInitialized() might be called concurrently in
|
jpayne@69
|
297 // another thread.
|
jpayne@69
|
298
|
jpayne@69
|
299 private:
|
jpayne@69
|
300 #if KJ_USE_FUTEX
|
jpayne@69
|
301 uint futex;
|
jpayne@69
|
302
|
jpayne@69
|
303 enum State {
|
jpayne@69
|
304 UNINITIALIZED,
|
jpayne@69
|
305 INITIALIZING,
|
jpayne@69
|
306 INITIALIZING_WITH_WAITERS,
|
jpayne@69
|
307 INITIALIZED
|
jpayne@69
|
308 };
|
jpayne@69
|
309
|
jpayne@69
|
310 #elif _WIN32 || __CYGWIN__
|
jpayne@69
|
311 uintptr_t initOnce; // Actually an INIT_ONCE, but don't want to #include <windows.h> in header.
|
jpayne@69
|
312
|
jpayne@69
|
313 #else
|
jpayne@69
|
314 enum State {
|
jpayne@69
|
315 UNINITIALIZED,
|
jpayne@69
|
316 INITIALIZED
|
jpayne@69
|
317 };
|
jpayne@69
|
318 State state;
|
jpayne@69
|
319 pthread_mutex_t mutex;
|
jpayne@69
|
320 #endif
|
jpayne@69
|
321 };
|
jpayne@69
|
322
|
jpayne@69
|
323 } // namespace _ (private)
|
jpayne@69
|
324
|
jpayne@69
|
325 // =======================================================================================
|
jpayne@69
|
326 // Public interface
|
jpayne@69
|
327
|
jpayne@69
|
328 template <typename T>
|
jpayne@69
|
329 class Locked {
|
jpayne@69
|
330 // Return type for `MutexGuarded<T>::lock()`. `Locked<T>` provides access to the bounded object
|
jpayne@69
|
331 // and unlocks the mutex when it goes out of scope.
|
jpayne@69
|
332
|
jpayne@69
|
333 public:
|
jpayne@69
|
334 KJ_DISALLOW_COPY(Locked);
|
jpayne@69
|
335 inline Locked(): mutex(nullptr), ptr(nullptr) {}
|
jpayne@69
|
336 inline Locked(Locked&& other): mutex(other.mutex), ptr(other.ptr) {
|
jpayne@69
|
337 other.mutex = nullptr;
|
jpayne@69
|
338 other.ptr = nullptr;
|
jpayne@69
|
339 }
|
jpayne@69
|
340 inline ~Locked() {
|
jpayne@69
|
341 if (mutex != nullptr) mutex->unlock(isConst<T>() ? _::Mutex::SHARED : _::Mutex::EXCLUSIVE);
|
jpayne@69
|
342 }
|
jpayne@69
|
343
|
jpayne@69
|
344 inline Locked& operator=(Locked&& other) {
|
jpayne@69
|
345 if (mutex != nullptr) mutex->unlock(isConst<T>() ? _::Mutex::SHARED : _::Mutex::EXCLUSIVE);
|
jpayne@69
|
346 mutex = other.mutex;
|
jpayne@69
|
347 ptr = other.ptr;
|
jpayne@69
|
348 other.mutex = nullptr;
|
jpayne@69
|
349 other.ptr = nullptr;
|
jpayne@69
|
350 return *this;
|
jpayne@69
|
351 }
|
jpayne@69
|
352
|
jpayne@69
|
353 inline void release() {
|
jpayne@69
|
354 if (mutex != nullptr) mutex->unlock(isConst<T>() ? _::Mutex::SHARED : _::Mutex::EXCLUSIVE);
|
jpayne@69
|
355 mutex = nullptr;
|
jpayne@69
|
356 ptr = nullptr;
|
jpayne@69
|
357 }
|
jpayne@69
|
358
|
jpayne@69
|
359 inline T* operator->() { return ptr; }
|
jpayne@69
|
360 inline const T* operator->() const { return ptr; }
|
jpayne@69
|
361 inline T& operator*() { return *ptr; }
|
jpayne@69
|
362 inline const T& operator*() const { return *ptr; }
|
jpayne@69
|
363 inline T* get() { return ptr; }
|
jpayne@69
|
364 inline const T* get() const { return ptr; }
|
jpayne@69
|
365 inline operator T*() { return ptr; }
|
jpayne@69
|
366 inline operator const T*() const { return ptr; }
|
jpayne@69
|
367
|
jpayne@69
|
368 template <typename Cond>
|
jpayne@69
|
369 void wait(Cond&& condition, Maybe<Duration> timeout = nullptr,
|
jpayne@69
|
370 LockSourceLocationArg location = {}) {
|
jpayne@69
|
371 // Unlocks the lock until `condition(state)` evaluates true (where `state` is type `const T&`
|
jpayne@69
|
372 // referencing the object protected by the lock).
|
jpayne@69
|
373
|
jpayne@69
|
374 // We can't wait on a shared lock because the internal bookkeeping needed for a wait requires
|
jpayne@69
|
375 // the protection of an exclusive lock.
|
jpayne@69
|
376 static_assert(!isConst<T>(), "cannot wait() on shared lock");
|
jpayne@69
|
377
|
jpayne@69
|
378 struct PredicateImpl final: public _::Mutex::Predicate {
|
jpayne@69
|
379 bool check() override {
|
jpayne@69
|
380 return condition(value);
|
jpayne@69
|
381 }
|
jpayne@69
|
382
|
jpayne@69
|
383 Cond&& condition;
|
jpayne@69
|
384 const T& value;
|
jpayne@69
|
385
|
jpayne@69
|
386 PredicateImpl(Cond&& condition, const T& value)
|
jpayne@69
|
387 : condition(kj::fwd<Cond>(condition)), value(value) {}
|
jpayne@69
|
388 };
|
jpayne@69
|
389
|
jpayne@69
|
390 PredicateImpl impl(kj::fwd<Cond>(condition), *ptr);
|
jpayne@69
|
391 mutex->wait(impl, timeout, location);
|
jpayne@69
|
392 }
|
jpayne@69
|
393
|
jpayne@69
|
394 private:
|
jpayne@69
|
395 _::Mutex* mutex;
|
jpayne@69
|
396 T* ptr;
|
jpayne@69
|
397
|
jpayne@69
|
398 inline Locked(_::Mutex& mutex, T& value): mutex(&mutex), ptr(&value) {}
|
jpayne@69
|
399
|
jpayne@69
|
400 template <typename U>
|
jpayne@69
|
401 friend class MutexGuarded;
|
jpayne@69
|
402 template <typename U>
|
jpayne@69
|
403 friend class ExternalMutexGuarded;
|
jpayne@69
|
404
|
jpayne@69
|
405 #if KJ_MUTEX_TEST
|
jpayne@69
|
406 public:
|
jpayne@69
|
407 #endif
|
jpayne@69
|
408 void induceSpuriousWakeupForTest() { mutex->induceSpuriousWakeupForTest(); }
|
jpayne@69
|
409 // Utility method for mutex-test.c++ which causes a spurious thread wakeup on all threads that
|
jpayne@69
|
410 // are waiting for a when() condition. Assuming correct implementation, all those threads should
|
jpayne@69
|
411 // immediately go back to sleep.
|
jpayne@69
|
412 };
|
jpayne@69
|
413
|
jpayne@69
|
414 template <typename T>
|
jpayne@69
|
415 class MutexGuarded {
|
jpayne@69
|
416 // An object of type T, bounded by a mutex. In order to access the object, you must lock it.
|
jpayne@69
|
417 //
|
jpayne@69
|
418 // Write locks are not "recursive" -- trying to lock again in a thread that already holds a lock
|
jpayne@69
|
419 // will deadlock. Recursive write locks are usually a sign of bad design.
|
jpayne@69
|
420 //
|
jpayne@69
|
421 // Unfortunately, **READ LOCKS ARE NOT RECURSIVE** either. Common sense says they should be.
|
jpayne@69
|
422 // But on many operating systems (BSD, OSX), recursively read-locking a pthread_rwlock is
|
jpayne@69
|
423 // actually unsafe. The problem is that writers are "prioritized" over readers, so a read lock
|
jpayne@69
|
424 // request will block if any write lock requests are outstanding. So, if thread A takes a read
|
jpayne@69
|
425 // lock, thread B requests a write lock (and starts waiting), and then thread A tries to take
|
jpayne@69
|
426 // another read lock recursively, the result is deadlock.
|
jpayne@69
|
427
|
jpayne@69
|
428 public:
|
jpayne@69
|
429 template <typename... Params>
|
jpayne@69
|
430 explicit MutexGuarded(Params&&... params);
|
jpayne@69
|
431 // Initialize the mutex-bounded object by passing the given parameters to its constructor.
|
jpayne@69
|
432
|
jpayne@69
|
433 Locked<T> lockExclusive(LockSourceLocationArg location = {}) const;
|
jpayne@69
|
434 // Exclusively locks the object and returns it. The returned `Locked<T>` can be passed by
|
jpayne@69
|
435 // move, similar to `Own<T>`.
|
jpayne@69
|
436 //
|
jpayne@69
|
437 // This method is declared `const` in accordance with KJ style rules which say that constness
|
jpayne@69
|
438 // should be used to indicate thread-safety. It is safe to share a const pointer between threads,
|
jpayne@69
|
439 // but it is not safe to share a mutable pointer. Since the whole point of MutexGuarded is to
|
jpayne@69
|
440 // be shared between threads, its methods should be const, even though locking it produces a
|
jpayne@69
|
441 // non-const pointer to the contained object.
|
jpayne@69
|
442
|
jpayne@69
|
443 Locked<const T> lockShared(LockSourceLocationArg location = {}) const;
|
jpayne@69
|
444 // Lock the value for shared access. Multiple shared locks can be taken concurrently, but cannot
|
jpayne@69
|
445 // be held at the same time as a non-shared lock.
|
jpayne@69
|
446
|
jpayne@69
|
447 Maybe<Locked<T>> lockExclusiveWithTimeout(Duration timeout,
|
jpayne@69
|
448 LockSourceLocationArg location = {}) const;
|
jpayne@69
|
449 // Attempts to exclusively lock the object. If the timeout elapses before the lock is acquired,
|
jpayne@69
|
450 // this returns null.
|
jpayne@69
|
451
|
jpayne@69
|
452 Maybe<Locked<const T>> lockSharedWithTimeout(Duration timeout,
|
jpayne@69
|
453 LockSourceLocationArg location = {}) const;
|
jpayne@69
|
454 // Attempts to lock the value for shared access. If the timeout elapses before the lock is acquired,
|
jpayne@69
|
455 // this returns null.
|
jpayne@69
|
456
|
jpayne@69
|
457 inline const T& getWithoutLock() const { return value; }
|
jpayne@69
|
458 inline T& getWithoutLock() { return value; }
|
jpayne@69
|
459 // Escape hatch for cases where some external factor guarantees that it's safe to get the
|
jpayne@69
|
460 // value. You should treat these like const_cast -- be highly suspicious of any use.
|
jpayne@69
|
461
|
jpayne@69
|
462 inline const T& getAlreadyLockedShared() const;
|
jpayne@69
|
463 inline T& getAlreadyLockedShared();
|
jpayne@69
|
464 inline T& getAlreadyLockedExclusive() const;
|
jpayne@69
|
465 // Like `getWithoutLock()`, but asserts that the lock is already held by the calling thread.
|
jpayne@69
|
466
|
jpayne@69
|
467 template <typename Cond, typename Func>
|
jpayne@69
|
468 auto when(Cond&& condition, Func&& callback, Maybe<Duration> timeout = nullptr,
|
jpayne@69
|
469 LockSourceLocationArg location = {}) const
|
jpayne@69
|
470 -> decltype(callback(instance<T&>())) {
|
jpayne@69
|
471 // Waits until condition(state) returns true, then calls callback(state) under lock.
|
jpayne@69
|
472 //
|
jpayne@69
|
473 // `condition`, when called, receives as its parameter a const reference to the state, which is
|
jpayne@69
|
474 // locked (either shared or exclusive). `callback` receives a mutable reference, which is
|
jpayne@69
|
475 // exclusively locked.
|
jpayne@69
|
476 //
|
jpayne@69
|
477 // `condition()` may be called multiple times, from multiple threads, while waiting for the
|
jpayne@69
|
478 // condition to become true. It may even return true once, but then be called more times.
|
jpayne@69
|
479 // It is guaranteed, though, that at the time `callback()` is finally called, `condition()`
|
jpayne@69
|
480 // would currently return true (assuming it is a pure function of the guarded data).
|
jpayne@69
|
481 //
|
jpayne@69
|
482 // If `timeout` is specified, then after the given amount of time, the callback will be called
|
jpayne@69
|
483 // regardless of whether the condition is true. In this case, when `callback()` is called,
|
jpayne@69
|
484 // `condition()` may in fact evaluate false, but *only* if the timeout was reached.
|
jpayne@69
|
485 //
|
jpayne@69
|
486 // TODO(cleanup): lock->wait() is a better interface. Can we deprecate this one?
|
jpayne@69
|
487
|
jpayne@69
|
488 auto lock = lockExclusive();
|
jpayne@69
|
489 lock.wait(kj::fwd<Cond>(condition), timeout, location);
|
jpayne@69
|
490 return callback(value);
|
jpayne@69
|
491 }
|
jpayne@69
|
492
|
jpayne@69
|
493 private:
|
jpayne@69
|
494 mutable _::Mutex mutex;
|
jpayne@69
|
495 mutable T value;
|
jpayne@69
|
496 };
|
jpayne@69
|
497
|
jpayne@69
|
498 template <typename T>
|
jpayne@69
|
499 class MutexGuarded<const T> {
|
jpayne@69
|
500 // MutexGuarded cannot guard a const type. This would be pointless anyway, and would complicate
|
jpayne@69
|
501 // the implementation of Locked<T>, which uses constness to decide what kind of lock it holds.
|
jpayne@69
|
502 static_assert(sizeof(T) < 0, "MutexGuarded's type cannot be const.");
|
jpayne@69
|
503 };
|
jpayne@69
|
504
|
jpayne@69
|
505 template <typename T>
|
jpayne@69
|
506 class ExternalMutexGuarded {
|
jpayne@69
|
507 // Holds a value that can only be manipulated while some other mutex is locked.
|
jpayne@69
|
508 //
|
jpayne@69
|
509 // The ExternalMutexGuarded<T> lives *outside* the scope of any lock on the mutex, but ensures
|
jpayne@69
|
510 // that the value it holds can only be accessed under lock by forcing the caller to present a
|
jpayne@69
|
511 // lock before accessing the value.
|
jpayne@69
|
512 //
|
jpayne@69
|
513 // Additionally, ExternalMutexGuarded<T>'s destructor will take an exclusive lock on the mutex
|
jpayne@69
|
514 // while destroying the held value, unless the value has been release()ed before hand.
|
jpayne@69
|
515 //
|
jpayne@69
|
516 // The type T must have the following properties (which probably all movable types satisfy):
|
jpayne@69
|
517 // - T is movable.
|
jpayne@69
|
518 // - Immediately after any of the following has happened, T's destructor is effectively a no-op
|
jpayne@69
|
519 // (hence certainly not requiring locks):
|
jpayne@69
|
520 // - The value has been default-constructed.
|
jpayne@69
|
521 // - The value has been initialized by-move from a default-constructed T.
|
jpayne@69
|
522 // - The value has been moved away.
|
jpayne@69
|
523 // - If ExternalMutexGuarded<T> is ever moved, then T must have a move constructor and move
|
jpayne@69
|
524 // assignment operator that do not follow any pointers, therefore do not need to take a lock.
|
jpayne@69
|
525 //
|
jpayne@69
|
526 // Inherits from LockSourceLocation to perform an empty base class optimization when lock tracking
|
jpayne@69
|
527 // is compiled out. Once the minimum C++ standard for the KJ library is C++20, this optimization
|
jpayne@69
|
528 // could be replaced by a member variable with a [[no_unique_address]] annotation.
|
jpayne@69
|
529 public:
|
jpayne@69
|
530 ExternalMutexGuarded(LockSourceLocationArg location = {})
|
jpayne@69
|
531 : location(location) {}
|
jpayne@69
|
532
|
jpayne@69
|
533 template <typename U, typename... Params>
|
jpayne@69
|
534 ExternalMutexGuarded(Locked<U> lock, Params&&... params, LockSourceLocationArg location = {})
|
jpayne@69
|
535 : mutex(lock.mutex),
|
jpayne@69
|
536 value(kj::fwd<Params>(params)...),
|
jpayne@69
|
537 location(location) {}
|
jpayne@69
|
538 // Construct the value in-place. This constructor requires passing ownership of the lock into
|
jpayne@69
|
539 // the constructor. Normally this should be a lock that you take on the line calling the
|
jpayne@69
|
540 // constructor, like:
|
jpayne@69
|
541 //
|
jpayne@69
|
542 // ExternalMutexGuarded<T> foo(someMutexGuarded.lockExclusive());
|
jpayne@69
|
543 //
|
jpayne@69
|
544 // The reason this constructor does not accept an lvalue reference to an existing lock is because
|
jpayne@69
|
545 // this would be deadlock-prone: If an exception were thrown immediately after the constructor
|
jpayne@69
|
546 // completed, then the destructor would deadlock, because the lock would still be held. An
|
jpayne@69
|
547 // ExternalMutexGuarded must live outside the scope of any locks to avoid such a deadlock.
|
jpayne@69
|
548
|
jpayne@69
|
549 ~ExternalMutexGuarded() noexcept(false) {
|
jpayne@69
|
550 if (mutex != nullptr) {
|
jpayne@69
|
551 mutex->lock(_::Mutex::EXCLUSIVE, nullptr, location);
|
jpayne@69
|
552 KJ_DEFER(mutex->unlock(_::Mutex::EXCLUSIVE));
|
jpayne@69
|
553 value = T();
|
jpayne@69
|
554 }
|
jpayne@69
|
555 }
|
jpayne@69
|
556
|
jpayne@69
|
557 ExternalMutexGuarded(ExternalMutexGuarded&& other)
|
jpayne@69
|
558 : mutex(other.mutex), value(kj::mv(other.value)), location(other.location) {
|
jpayne@69
|
559 other.mutex = nullptr;
|
jpayne@69
|
560 }
|
jpayne@69
|
561 ExternalMutexGuarded& operator=(ExternalMutexGuarded&& other) {
|
jpayne@69
|
562 mutex = other.mutex;
|
jpayne@69
|
563 value = kj::mv(other.value);
|
jpayne@69
|
564 location = other.location;
|
jpayne@69
|
565 other.mutex = nullptr;
|
jpayne@69
|
566 return *this;
|
jpayne@69
|
567 }
|
jpayne@69
|
568
|
jpayne@69
|
569 template <typename U>
|
jpayne@69
|
570 void set(Locked<U>& lock, T&& newValue) {
|
jpayne@69
|
571 KJ_IREQUIRE(mutex == nullptr);
|
jpayne@69
|
572 mutex = lock.mutex;
|
jpayne@69
|
573 value = kj::mv(newValue);
|
jpayne@69
|
574 }
|
jpayne@69
|
575
|
jpayne@69
|
576 template <typename U>
|
jpayne@69
|
577 T& get(Locked<U>& lock) {
|
jpayne@69
|
578 KJ_IREQUIRE(lock.mutex == mutex);
|
jpayne@69
|
579 return value;
|
jpayne@69
|
580 }
|
jpayne@69
|
581
|
jpayne@69
|
582 template <typename U>
|
jpayne@69
|
583 const T& get(Locked<const U>& lock) const {
|
jpayne@69
|
584 KJ_IREQUIRE(lock.mutex == mutex);
|
jpayne@69
|
585 return value;
|
jpayne@69
|
586 }
|
jpayne@69
|
587
|
jpayne@69
|
588 template <typename U>
|
jpayne@69
|
589 T release(Locked<U>& lock) {
|
jpayne@69
|
590 // Release (move away) the value. This allows the destructor to skip locking the mutex.
|
jpayne@69
|
591 KJ_IREQUIRE(lock.mutex == mutex);
|
jpayne@69
|
592 T result = kj::mv(value);
|
jpayne@69
|
593 mutex = nullptr;
|
jpayne@69
|
594 return result;
|
jpayne@69
|
595 }
|
jpayne@69
|
596
|
jpayne@69
|
597 private:
|
jpayne@69
|
598 _::Mutex* mutex = nullptr;
|
jpayne@69
|
599 T value;
|
jpayne@69
|
600 KJ_NO_UNIQUE_ADDRESS LockSourceLocation location;
|
jpayne@69
|
601 // When built against C++20 (or clang >= 9.0), the overhead of this is elided. Otherwise this
|
jpayne@69
|
602 // struct will be 1 byte larger than it would otherwise be.
|
jpayne@69
|
603 };
|
jpayne@69
|
604
|
jpayne@69
|
605 template <typename T>
|
jpayne@69
|
606 class Lazy {
|
jpayne@69
|
607 // A lazily-initialized value.
|
jpayne@69
|
608
|
jpayne@69
|
609 public:
|
jpayne@69
|
610 template <typename Func>
|
jpayne@69
|
611 T& get(Func&& init, LockSourceLocationArg location = {});
|
jpayne@69
|
612 template <typename Func>
|
jpayne@69
|
613 const T& get(Func&& init, LockSourceLocationArg location = {}) const;
|
jpayne@69
|
614 // The first thread to call get() will invoke the given init function to construct the value.
|
jpayne@69
|
615 // Other threads will block until construction completes, then return the same value.
|
jpayne@69
|
616 //
|
jpayne@69
|
617 // `init` is a functor(typically a lambda) which takes `SpaceFor<T>&` as its parameter and returns
|
jpayne@69
|
618 // `Own<T>`. If `init` throws an exception, the exception is propagated out of that thread's
|
jpayne@69
|
619 // call to `get()`, and subsequent calls behave as if `get()` hadn't been called at all yet --
|
jpayne@69
|
620 // in other words, subsequent calls retry initialization until it succeeds.
|
jpayne@69
|
621
|
jpayne@69
|
622 private:
|
jpayne@69
|
623 mutable _::Once once;
|
jpayne@69
|
624 mutable SpaceFor<T> space;
|
jpayne@69
|
625 mutable Own<T> value;
|
jpayne@69
|
626
|
jpayne@69
|
627 template <typename Func>
|
jpayne@69
|
628 class InitImpl;
|
jpayne@69
|
629 };
|
jpayne@69
|
630
|
jpayne@69
|
631 // =======================================================================================
|
jpayne@69
|
632 // Inline implementation details
|
jpayne@69
|
633
|
jpayne@69
|
634 template <typename T>
|
jpayne@69
|
635 template <typename... Params>
|
jpayne@69
|
636 inline MutexGuarded<T>::MutexGuarded(Params&&... params)
|
jpayne@69
|
637 : value(kj::fwd<Params>(params)...) {}
|
jpayne@69
|
638
|
jpayne@69
|
639 template <typename T>
|
jpayne@69
|
640 inline Locked<T> MutexGuarded<T>::lockExclusive(LockSourceLocationArg location)
|
jpayne@69
|
641 const {
|
jpayne@69
|
642 mutex.lock(_::Mutex::EXCLUSIVE, nullptr, location);
|
jpayne@69
|
643 return Locked<T>(mutex, value);
|
jpayne@69
|
644 }
|
jpayne@69
|
645
|
jpayne@69
|
646 template <typename T>
|
jpayne@69
|
647 inline Locked<const T> MutexGuarded<T>::lockShared(LockSourceLocationArg location) const {
|
jpayne@69
|
648 mutex.lock(_::Mutex::SHARED, nullptr, location);
|
jpayne@69
|
649 return Locked<const T>(mutex, value);
|
jpayne@69
|
650 }
|
jpayne@69
|
651
|
jpayne@69
|
652 template <typename T>
|
jpayne@69
|
653 inline Maybe<Locked<T>> MutexGuarded<T>::lockExclusiveWithTimeout(Duration timeout,
|
jpayne@69
|
654 LockSourceLocationArg location) const {
|
jpayne@69
|
655 if (mutex.lock(_::Mutex::EXCLUSIVE, timeout, location)) {
|
jpayne@69
|
656 return Locked<T>(mutex, value);
|
jpayne@69
|
657 } else {
|
jpayne@69
|
658 return nullptr;
|
jpayne@69
|
659 }
|
jpayne@69
|
660 }
|
jpayne@69
|
661
|
jpayne@69
|
662 template <typename T>
|
jpayne@69
|
663 inline Maybe<Locked<const T>> MutexGuarded<T>::lockSharedWithTimeout(Duration timeout,
|
jpayne@69
|
664 LockSourceLocationArg location) const {
|
jpayne@69
|
665 if (mutex.lock(_::Mutex::SHARED, timeout, location)) {
|
jpayne@69
|
666 return Locked<const T>(mutex, value);
|
jpayne@69
|
667 } else {
|
jpayne@69
|
668 return nullptr;
|
jpayne@69
|
669 }
|
jpayne@69
|
670 }
|
jpayne@69
|
671
|
jpayne@69
|
672 template <typename T>
|
jpayne@69
|
673 inline const T& MutexGuarded<T>::getAlreadyLockedShared() const {
|
jpayne@69
|
674 #ifdef KJ_DEBUG
|
jpayne@69
|
675 mutex.assertLockedByCaller(_::Mutex::SHARED);
|
jpayne@69
|
676 #endif
|
jpayne@69
|
677 return value;
|
jpayne@69
|
678 }
|
jpayne@69
|
679 template <typename T>
|
jpayne@69
|
680 inline T& MutexGuarded<T>::getAlreadyLockedShared() {
|
jpayne@69
|
681 #ifdef KJ_DEBUG
|
jpayne@69
|
682 mutex.assertLockedByCaller(_::Mutex::SHARED);
|
jpayne@69
|
683 #endif
|
jpayne@69
|
684 return value;
|
jpayne@69
|
685 }
|
jpayne@69
|
686 template <typename T>
|
jpayne@69
|
687 inline T& MutexGuarded<T>::getAlreadyLockedExclusive() const {
|
jpayne@69
|
688 #ifdef KJ_DEBUG
|
jpayne@69
|
689 mutex.assertLockedByCaller(_::Mutex::EXCLUSIVE);
|
jpayne@69
|
690 #endif
|
jpayne@69
|
691 return const_cast<T&>(value);
|
jpayne@69
|
692 }
|
jpayne@69
|
693
|
jpayne@69
|
694 template <typename T>
|
jpayne@69
|
695 template <typename Func>
|
jpayne@69
|
696 class Lazy<T>::InitImpl: public _::Once::Initializer {
|
jpayne@69
|
697 public:
|
jpayne@69
|
698 inline InitImpl(const Lazy<T>& lazy, Func&& func): lazy(lazy), func(kj::fwd<Func>(func)) {}
|
jpayne@69
|
699
|
jpayne@69
|
700 void run() override {
|
jpayne@69
|
701 lazy.value = func(lazy.space);
|
jpayne@69
|
702 }
|
jpayne@69
|
703
|
jpayne@69
|
704 private:
|
jpayne@69
|
705 const Lazy<T>& lazy;
|
jpayne@69
|
706 Func func;
|
jpayne@69
|
707 };
|
jpayne@69
|
708
|
jpayne@69
|
709 template <typename T>
|
jpayne@69
|
710 template <typename Func>
|
jpayne@69
|
711 inline T& Lazy<T>::get(Func&& init, LockSourceLocationArg location) {
|
jpayne@69
|
712 if (!once.isInitialized()) {
|
jpayne@69
|
713 InitImpl<Func> initImpl(*this, kj::fwd<Func>(init));
|
jpayne@69
|
714 once.runOnce(initImpl, location);
|
jpayne@69
|
715 }
|
jpayne@69
|
716 return *value;
|
jpayne@69
|
717 }
|
jpayne@69
|
718
|
jpayne@69
|
719 template <typename T>
|
jpayne@69
|
720 template <typename Func>
|
jpayne@69
|
721 inline const T& Lazy<T>::get(Func&& init, LockSourceLocationArg location) const {
|
jpayne@69
|
722 if (!once.isInitialized()) {
|
jpayne@69
|
723 InitImpl<Func> initImpl(*this, kj::fwd<Func>(init));
|
jpayne@69
|
724 once.runOnce(initImpl, location);
|
jpayne@69
|
725 }
|
jpayne@69
|
726 return *value;
|
jpayne@69
|
727 }
|
jpayne@69
|
728
|
jpayne@69
|
729 #if KJ_TRACK_LOCK_BLOCKING
|
jpayne@69
|
730 struct BlockedOnMutexAcquisition {
|
jpayne@69
|
731 const _::Mutex& mutex;
|
jpayne@69
|
732 // The mutex we are blocked on.
|
jpayne@69
|
733
|
jpayne@69
|
734 const SourceLocation& origin;
|
jpayne@69
|
735 // Where did the blocking operation originate from.
|
jpayne@69
|
736 };
|
jpayne@69
|
737
|
jpayne@69
|
738 struct BlockedOnCondVarWait {
|
jpayne@69
|
739 const _::Mutex& mutex;
|
jpayne@69
|
740 // The mutex the condition variable is using (may or may not be locked).
|
jpayne@69
|
741
|
jpayne@69
|
742 const void* waiter;
|
jpayne@69
|
743 // Pointer to the waiter that's being waited on.
|
jpayne@69
|
744
|
jpayne@69
|
745 const SourceLocation& origin;
|
jpayne@69
|
746 // Where did the blocking operation originate from.
|
jpayne@69
|
747 };
|
jpayne@69
|
748
|
jpayne@69
|
749 struct BlockedOnOnceInit {
|
jpayne@69
|
750 const _::Once& once;
|
jpayne@69
|
751
|
jpayne@69
|
752 const SourceLocation& origin;
|
jpayne@69
|
753 // Where did the blocking operation originate from.
|
jpayne@69
|
754 };
|
jpayne@69
|
755
|
jpayne@69
|
756 using BlockedOnReason = OneOf<BlockedOnMutexAcquisition, BlockedOnCondVarWait, BlockedOnOnceInit>;
|
jpayne@69
|
757
|
jpayne@69
|
758 Maybe<const BlockedOnReason&> blockedReason() noexcept;
|
jpayne@69
|
759 // Returns the information about the reason the current thread is blocked synchronously on KJ
|
jpayne@69
|
760 // lock primitives. Returns nullptr if the current thread is not currently blocked on such
|
jpayne@69
|
761 // primitives. This is intended to be called from a signal handler to check whether the current
|
jpayne@69
|
762 // thread is blocked. Outside of a signal handler there is little value to this function. In those
|
jpayne@69
|
763 // cases by definition the thread is not blocked. This includes the callable used as part of a
|
jpayne@69
|
764 // condition variable since that happens after the lock is acquired & the current thread is no
|
jpayne@69
|
765 // longer blocked). The utility could be made useful for non-signal handler use-cases by being able
|
jpayne@69
|
766 // to fetch the pointer to the TLS variable directly (i.e. const BlockedOnReason&*). However, there
|
jpayne@69
|
767 // would have to be additional changes/complexity to try make that work since you'd need
|
jpayne@69
|
768 // synchronization to ensure that the memory you'd try to reference is still valid. The likely
|
jpayne@69
|
769 // solution would be to make these mutually exclusive options where you can use either the fast
|
jpayne@69
|
770 // async-safe option, or a mutex-guarded TLS variable you can get a reference to that isn't
|
jpayne@69
|
771 // async-safe. That being said, maybe someone can come up with a way to make something that works
|
jpayne@69
|
772 // in both use-cases which would of course be more preferable.
|
jpayne@69
|
773 #endif
|
jpayne@69
|
774
|
jpayne@69
|
775
|
jpayne@69
|
776 } // namespace kj
|
jpayne@69
|
777
|
jpayne@69
|
778 KJ_END_HEADER
|