annotate CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/include/kj/mutex.h @ 69:33d812a61356

planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author jpayne
date Tue, 18 Mar 2025 17:55:14 -0400
parents
children
rev   line source
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