Mercurial > repos > rliterman > csp2
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/include/kj/mutex.h Tue Mar 18 17:55:14 2025 -0400 @@ -0,0 +1,778 @@ +// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors +// Licensed under the MIT License: +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +#pragma once + +#include "debug.h" +#include "memory.h" +#include <inttypes.h> +#include "time.h" +#include "source-location.h" +#include "one-of.h" + +KJ_BEGIN_HEADER + +#if __linux__ && !defined(KJ_USE_FUTEX) +#define KJ_USE_FUTEX 1 +#endif + +#if !KJ_USE_FUTEX && !_WIN32 && !__CYGWIN__ +// We fall back to pthreads when we don't have a better platform-specific primitive. pthreads +// mutexes are bloated, though, so we like to avoid them. Hence on Linux we use futex(), and on +// Windows we use SRW locks and friends. On Cygwin we prefer the Win32 primitives both because they +// are more efficient and because I ran into problems with Cygwin's implementation of RW locks +// seeming to allow multiple threads to lock the same mutex (but I didn't investigate very +// closely). +// +// TODO(someday): Write efficient low-level locking primitives for other platforms. +#include <pthread.h> +#endif + +// There are 3 macros controlling lock tracking: +// KJ_TRACK_LOCK_BLOCKING will set up async signal safe TLS variables that can be used to identify +// the KJ primitive blocking the current thread. +// KJ_SAVE_ACQUIRED_LOCK_INFO will allow introspection of a Mutex to get information about what is +// currently holding the lock. +// KJ_TRACK_LOCK_ACQUISITION is automatically enabled by either one of them. + +#if KJ_TRACK_LOCK_BLOCKING +// Lock tracking is required to keep track of what blocked. +#define KJ_TRACK_LOCK_ACQUISITION 1 +#endif + +#if KJ_SAVE_ACQUIRED_LOCK_INFO +#define KJ_TRACK_LOCK_ACQUISITION 1 +#include <unistd.h> +#endif + +namespace kj { +#if KJ_TRACK_LOCK_ACQUISITION +#if !KJ_USE_FUTEX +#error Lock tracking is only currently supported for futex-based mutexes. +#endif + +#if !KJ_COMPILER_SUPPORTS_SOURCE_LOCATION +#error C++20 or newer is required (or the use of clang/gcc). +#endif + +using LockSourceLocation = SourceLocation; +using LockSourceLocationArg = const SourceLocation&; +// On x86-64 the codegen is optimal if the argument has type const& for the location. However, +// since this conflicts with the optimal call signature for NoopSourceLocation, +// LockSourceLocationArg is used to conditionally select the right type without polluting the usage +// themselves. Interestingly this makes no difference on ARM. +// https://godbolt.org/z/q6G8ee5a3 +#else +using LockSourceLocation = NoopSourceLocation; +using LockSourceLocationArg = NoopSourceLocation; +#endif + + +class Exception; + +// ======================================================================================= +// Private details -- public interfaces follow below. + +namespace _ { // private + +#if KJ_SAVE_ACQUIRED_LOCK_INFO +class HoldingExclusively { + // The lock is being held in exclusive mode. +public: + constexpr HoldingExclusively(pid_t tid, const SourceLocation& location) + : heldBy(tid), acquiredAt(location) {} + + pid_t threadHoldingLock() const { return heldBy; } + const SourceLocation& lockAcquiredAt() const { return acquiredAt; } + +private: + pid_t heldBy; + SourceLocation acquiredAt; +}; + +class HoldingShared { + // The lock is being held in shared mode currently. Which threads are holding this lock open + // is unknown. +public: + constexpr HoldingShared(const SourceLocation& location) : acquiredAt(location) {} + + const SourceLocation& lockAcquiredAt() const { return acquiredAt; } + +private: + SourceLocation acquiredAt; +}; +#endif + +class Mutex { + // Internal implementation details. See `MutexGuarded<T>`. + + struct Waiter; + +public: + Mutex(); + ~Mutex(); + KJ_DISALLOW_COPY_AND_MOVE(Mutex); + + enum Exclusivity { + EXCLUSIVE, + SHARED + }; + + bool lock(Exclusivity exclusivity, Maybe<Duration> timeout, LockSourceLocationArg location); + void unlock(Exclusivity exclusivity, Waiter* waiterToSkip = nullptr); + + void assertLockedByCaller(Exclusivity exclusivity) const; + // In debug mode, assert that the mutex is locked by the calling thread, or if that is + // non-trivial, assert that the mutex is locked (which should be good enough to catch problems + // in unit tests). In non-debug builds, do nothing. + + class Predicate { + public: + virtual bool check() = 0; + }; + + void wait(Predicate& predicate, Maybe<Duration> timeout, LockSourceLocationArg location); + // If predicate.check() returns false, unlock the mutex until predicate.check() returns true, or + // when the timeout (if any) expires. The mutex is always re-locked when this returns regardless + // of whether the timeout expired, and including if it throws. + // + // Requires that the mutex is already exclusively locked before calling. + + void induceSpuriousWakeupForTest(); + // Utility method for mutex-test.c++ which causes a spurious thread wakeup on all threads that + // are waiting for a wait() condition. Assuming correct implementation, all those threads + // should immediately go back to sleep. + +#if KJ_USE_FUTEX + uint numReadersWaitingForTest() const; + // The number of reader locks that are currently blocked on this lock (must be called while + // holding the writer lock). This is really only a utility method for mutex-test.c++ so it can + // validate certain invariants. +#endif + +#if KJ_SAVE_ACQUIRED_LOCK_INFO + using AcquiredMetadata = kj::OneOf<HoldingExclusively, HoldingShared>; + KJ_DISABLE_TSAN AcquiredMetadata lockedInfo() const; + // Returns metadata about this lock when its held. This method is async signal safe. It must also + // be called in a state where it's guaranteed that the lock state won't be released by another + // thread. In other words this has to be called from the signal handler within the thread that's + // holding the lock. +#endif + +private: +#if KJ_USE_FUTEX + uint futex; + // bit 31 (msb) = set if exclusive lock held + // bit 30 (msb) = set if threads are waiting for exclusive lock + // bits 0-29 = count of readers; If an exclusive lock is held, this is the count of threads + // waiting for a read lock, otherwise it is the count of threads that currently hold a read + // lock. + +#ifdef KJ_CONTENTION_WARNING_THRESHOLD + bool printContendedReader = false; +#endif + + static constexpr uint EXCLUSIVE_HELD = 1u << 31; + static constexpr uint EXCLUSIVE_REQUESTED = 1u << 30; + static constexpr uint SHARED_COUNT_MASK = EXCLUSIVE_REQUESTED - 1; + +#elif _WIN32 || __CYGWIN__ + uintptr_t srwLock; // Actually an SRWLOCK, but don't want to #include <windows.h> in header. + +#else + mutable pthread_rwlock_t mutex; +#endif + +#if KJ_SAVE_ACQUIRED_LOCK_INFO + pid_t lockedExclusivelyByThread = 0; + SourceLocation lockAcquiredLocation; + + KJ_DISABLE_TSAN void acquiredExclusive(pid_t tid, const SourceLocation& location) noexcept { + lockAcquiredLocation = location; + __atomic_store_n(&lockedExclusivelyByThread, tid, __ATOMIC_RELAXED); + } + + KJ_DISABLE_TSAN void acquiredShared(const SourceLocation& location) noexcept { + lockAcquiredLocation = location; + } + + KJ_DISABLE_TSAN SourceLocation releasingExclusive() noexcept { + auto tmp = lockAcquiredLocation; + lockAcquiredLocation = SourceLocation{}; + lockedExclusivelyByThread = 0; + return tmp; + } +#else + static constexpr void acquiredExclusive(uint, LockSourceLocationArg) {} + static constexpr void acquiredShared(LockSourceLocationArg) {} + static constexpr NoopSourceLocation releasingExclusive() { return NoopSourceLocation{}; } +#endif + struct Waiter { + kj::Maybe<Waiter&> next; + kj::Maybe<Waiter&>* prev; + Predicate& predicate; + Maybe<Own<Exception>> exception; +#if KJ_USE_FUTEX + uint futex; + bool hasTimeout; +#elif _WIN32 || __CYGWIN__ + uintptr_t condvar; + // Actually CONDITION_VARIABLE, but don't want to #include <windows.h> in header. +#else + pthread_cond_t condvar; + + pthread_mutex_t stupidMutex; + // pthread condvars are only compatible with basic pthread mutexes, not rwlocks, for no + // particularly good reason. To work around this, we need an extra mutex per condvar. +#endif + }; + + kj::Maybe<Waiter&> waitersHead = nullptr; + kj::Maybe<Waiter&>* waitersTail = &waitersHead; + // linked list of waiters; can only modify under lock + + inline void addWaiter(Waiter& waiter); + inline void removeWaiter(Waiter& waiter); + bool checkPredicate(Waiter& waiter); +#if _WIN32 || __CYGWIN__ + void wakeReadyWaiter(Waiter* waiterToSkip); +#endif +}; + +class Once { + // Internal implementation details. See `Lazy<T>`. + +public: +#if KJ_USE_FUTEX + inline Once(bool startInitialized = false) + : futex(startInitialized ? INITIALIZED : UNINITIALIZED) {} +#else + Once(bool startInitialized = false); + ~Once(); +#endif + KJ_DISALLOW_COPY_AND_MOVE(Once); + + class Initializer { + public: + virtual void run() = 0; + }; + + void runOnce(Initializer& init, LockSourceLocationArg location); + +#if _WIN32 || __CYGWIN__ // TODO(perf): Can we make this inline on win32 somehow? + bool isInitialized() noexcept; + +#else + inline bool isInitialized() noexcept { + // Fast path check to see if runOnce() would simply return immediately. +#if KJ_USE_FUTEX + return __atomic_load_n(&futex, __ATOMIC_ACQUIRE) == INITIALIZED; +#else + return __atomic_load_n(&state, __ATOMIC_ACQUIRE) == INITIALIZED; +#endif + } +#endif + + void reset(); + // Returns the state from initialized to uninitialized. It is an error to call this when + // not already initialized, or when runOnce() or isInitialized() might be called concurrently in + // another thread. + +private: +#if KJ_USE_FUTEX + uint futex; + + enum State { + UNINITIALIZED, + INITIALIZING, + INITIALIZING_WITH_WAITERS, + INITIALIZED + }; + +#elif _WIN32 || __CYGWIN__ + uintptr_t initOnce; // Actually an INIT_ONCE, but don't want to #include <windows.h> in header. + +#else + enum State { + UNINITIALIZED, + INITIALIZED + }; + State state; + pthread_mutex_t mutex; +#endif +}; + +} // namespace _ (private) + +// ======================================================================================= +// Public interface + +template <typename T> +class Locked { + // Return type for `MutexGuarded<T>::lock()`. `Locked<T>` provides access to the bounded object + // and unlocks the mutex when it goes out of scope. + +public: + KJ_DISALLOW_COPY(Locked); + inline Locked(): mutex(nullptr), ptr(nullptr) {} + inline Locked(Locked&& other): mutex(other.mutex), ptr(other.ptr) { + other.mutex = nullptr; + other.ptr = nullptr; + } + inline ~Locked() { + if (mutex != nullptr) mutex->unlock(isConst<T>() ? _::Mutex::SHARED : _::Mutex::EXCLUSIVE); + } + + inline Locked& operator=(Locked&& other) { + if (mutex != nullptr) mutex->unlock(isConst<T>() ? _::Mutex::SHARED : _::Mutex::EXCLUSIVE); + mutex = other.mutex; + ptr = other.ptr; + other.mutex = nullptr; + other.ptr = nullptr; + return *this; + } + + inline void release() { + if (mutex != nullptr) mutex->unlock(isConst<T>() ? _::Mutex::SHARED : _::Mutex::EXCLUSIVE); + mutex = nullptr; + ptr = nullptr; + } + + inline T* operator->() { return ptr; } + inline const T* operator->() const { return ptr; } + inline T& operator*() { return *ptr; } + inline const T& operator*() const { return *ptr; } + inline T* get() { return ptr; } + inline const T* get() const { return ptr; } + inline operator T*() { return ptr; } + inline operator const T*() const { return ptr; } + + template <typename Cond> + void wait(Cond&& condition, Maybe<Duration> timeout = nullptr, + LockSourceLocationArg location = {}) { + // Unlocks the lock until `condition(state)` evaluates true (where `state` is type `const T&` + // referencing the object protected by the lock). + + // We can't wait on a shared lock because the internal bookkeeping needed for a wait requires + // the protection of an exclusive lock. + static_assert(!isConst<T>(), "cannot wait() on shared lock"); + + struct PredicateImpl final: public _::Mutex::Predicate { + bool check() override { + return condition(value); + } + + Cond&& condition; + const T& value; + + PredicateImpl(Cond&& condition, const T& value) + : condition(kj::fwd<Cond>(condition)), value(value) {} + }; + + PredicateImpl impl(kj::fwd<Cond>(condition), *ptr); + mutex->wait(impl, timeout, location); + } + +private: + _::Mutex* mutex; + T* ptr; + + inline Locked(_::Mutex& mutex, T& value): mutex(&mutex), ptr(&value) {} + + template <typename U> + friend class MutexGuarded; + template <typename U> + friend class ExternalMutexGuarded; + +#if KJ_MUTEX_TEST +public: +#endif + void induceSpuriousWakeupForTest() { mutex->induceSpuriousWakeupForTest(); } + // Utility method for mutex-test.c++ which causes a spurious thread wakeup on all threads that + // are waiting for a when() condition. Assuming correct implementation, all those threads should + // immediately go back to sleep. +}; + +template <typename T> +class MutexGuarded { + // An object of type T, bounded by a mutex. In order to access the object, you must lock it. + // + // Write locks are not "recursive" -- trying to lock again in a thread that already holds a lock + // will deadlock. Recursive write locks are usually a sign of bad design. + // + // Unfortunately, **READ LOCKS ARE NOT RECURSIVE** either. Common sense says they should be. + // But on many operating systems (BSD, OSX), recursively read-locking a pthread_rwlock is + // actually unsafe. The problem is that writers are "prioritized" over readers, so a read lock + // request will block if any write lock requests are outstanding. So, if thread A takes a read + // lock, thread B requests a write lock (and starts waiting), and then thread A tries to take + // another read lock recursively, the result is deadlock. + +public: + template <typename... Params> + explicit MutexGuarded(Params&&... params); + // Initialize the mutex-bounded object by passing the given parameters to its constructor. + + Locked<T> lockExclusive(LockSourceLocationArg location = {}) const; + // Exclusively locks the object and returns it. The returned `Locked<T>` can be passed by + // move, similar to `Own<T>`. + // + // This method is declared `const` in accordance with KJ style rules which say that constness + // should be used to indicate thread-safety. It is safe to share a const pointer between threads, + // but it is not safe to share a mutable pointer. Since the whole point of MutexGuarded is to + // be shared between threads, its methods should be const, even though locking it produces a + // non-const pointer to the contained object. + + Locked<const T> lockShared(LockSourceLocationArg location = {}) const; + // Lock the value for shared access. Multiple shared locks can be taken concurrently, but cannot + // be held at the same time as a non-shared lock. + + Maybe<Locked<T>> lockExclusiveWithTimeout(Duration timeout, + LockSourceLocationArg location = {}) const; + // Attempts to exclusively lock the object. If the timeout elapses before the lock is acquired, + // this returns null. + + Maybe<Locked<const T>> lockSharedWithTimeout(Duration timeout, + LockSourceLocationArg location = {}) const; + // Attempts to lock the value for shared access. If the timeout elapses before the lock is acquired, + // this returns null. + + inline const T& getWithoutLock() const { return value; } + inline T& getWithoutLock() { return value; } + // Escape hatch for cases where some external factor guarantees that it's safe to get the + // value. You should treat these like const_cast -- be highly suspicious of any use. + + inline const T& getAlreadyLockedShared() const; + inline T& getAlreadyLockedShared(); + inline T& getAlreadyLockedExclusive() const; + // Like `getWithoutLock()`, but asserts that the lock is already held by the calling thread. + + template <typename Cond, typename Func> + auto when(Cond&& condition, Func&& callback, Maybe<Duration> timeout = nullptr, + LockSourceLocationArg location = {}) const + -> decltype(callback(instance<T&>())) { + // Waits until condition(state) returns true, then calls callback(state) under lock. + // + // `condition`, when called, receives as its parameter a const reference to the state, which is + // locked (either shared or exclusive). `callback` receives a mutable reference, which is + // exclusively locked. + // + // `condition()` may be called multiple times, from multiple threads, while waiting for the + // condition to become true. It may even return true once, but then be called more times. + // It is guaranteed, though, that at the time `callback()` is finally called, `condition()` + // would currently return true (assuming it is a pure function of the guarded data). + // + // If `timeout` is specified, then after the given amount of time, the callback will be called + // regardless of whether the condition is true. In this case, when `callback()` is called, + // `condition()` may in fact evaluate false, but *only* if the timeout was reached. + // + // TODO(cleanup): lock->wait() is a better interface. Can we deprecate this one? + + auto lock = lockExclusive(); + lock.wait(kj::fwd<Cond>(condition), timeout, location); + return callback(value); + } + +private: + mutable _::Mutex mutex; + mutable T value; +}; + +template <typename T> +class MutexGuarded<const T> { + // MutexGuarded cannot guard a const type. This would be pointless anyway, and would complicate + // the implementation of Locked<T>, which uses constness to decide what kind of lock it holds. + static_assert(sizeof(T) < 0, "MutexGuarded's type cannot be const."); +}; + +template <typename T> +class ExternalMutexGuarded { + // Holds a value that can only be manipulated while some other mutex is locked. + // + // The ExternalMutexGuarded<T> lives *outside* the scope of any lock on the mutex, but ensures + // that the value it holds can only be accessed under lock by forcing the caller to present a + // lock before accessing the value. + // + // Additionally, ExternalMutexGuarded<T>'s destructor will take an exclusive lock on the mutex + // while destroying the held value, unless the value has been release()ed before hand. + // + // The type T must have the following properties (which probably all movable types satisfy): + // - T is movable. + // - Immediately after any of the following has happened, T's destructor is effectively a no-op + // (hence certainly not requiring locks): + // - The value has been default-constructed. + // - The value has been initialized by-move from a default-constructed T. + // - The value has been moved away. + // - If ExternalMutexGuarded<T> is ever moved, then T must have a move constructor and move + // assignment operator that do not follow any pointers, therefore do not need to take a lock. + // + // Inherits from LockSourceLocation to perform an empty base class optimization when lock tracking + // is compiled out. Once the minimum C++ standard for the KJ library is C++20, this optimization + // could be replaced by a member variable with a [[no_unique_address]] annotation. +public: + ExternalMutexGuarded(LockSourceLocationArg location = {}) + : location(location) {} + + template <typename U, typename... Params> + ExternalMutexGuarded(Locked<U> lock, Params&&... params, LockSourceLocationArg location = {}) + : mutex(lock.mutex), + value(kj::fwd<Params>(params)...), + location(location) {} + // Construct the value in-place. This constructor requires passing ownership of the lock into + // the constructor. Normally this should be a lock that you take on the line calling the + // constructor, like: + // + // ExternalMutexGuarded<T> foo(someMutexGuarded.lockExclusive()); + // + // The reason this constructor does not accept an lvalue reference to an existing lock is because + // this would be deadlock-prone: If an exception were thrown immediately after the constructor + // completed, then the destructor would deadlock, because the lock would still be held. An + // ExternalMutexGuarded must live outside the scope of any locks to avoid such a deadlock. + + ~ExternalMutexGuarded() noexcept(false) { + if (mutex != nullptr) { + mutex->lock(_::Mutex::EXCLUSIVE, nullptr, location); + KJ_DEFER(mutex->unlock(_::Mutex::EXCLUSIVE)); + value = T(); + } + } + + ExternalMutexGuarded(ExternalMutexGuarded&& other) + : mutex(other.mutex), value(kj::mv(other.value)), location(other.location) { + other.mutex = nullptr; + } + ExternalMutexGuarded& operator=(ExternalMutexGuarded&& other) { + mutex = other.mutex; + value = kj::mv(other.value); + location = other.location; + other.mutex = nullptr; + return *this; + } + + template <typename U> + void set(Locked<U>& lock, T&& newValue) { + KJ_IREQUIRE(mutex == nullptr); + mutex = lock.mutex; + value = kj::mv(newValue); + } + + template <typename U> + T& get(Locked<U>& lock) { + KJ_IREQUIRE(lock.mutex == mutex); + return value; + } + + template <typename U> + const T& get(Locked<const U>& lock) const { + KJ_IREQUIRE(lock.mutex == mutex); + return value; + } + + template <typename U> + T release(Locked<U>& lock) { + // Release (move away) the value. This allows the destructor to skip locking the mutex. + KJ_IREQUIRE(lock.mutex == mutex); + T result = kj::mv(value); + mutex = nullptr; + return result; + } + +private: + _::Mutex* mutex = nullptr; + T value; + KJ_NO_UNIQUE_ADDRESS LockSourceLocation location; + // When built against C++20 (or clang >= 9.0), the overhead of this is elided. Otherwise this + // struct will be 1 byte larger than it would otherwise be. +}; + +template <typename T> +class Lazy { + // A lazily-initialized value. + +public: + template <typename Func> + T& get(Func&& init, LockSourceLocationArg location = {}); + template <typename Func> + const T& get(Func&& init, LockSourceLocationArg location = {}) const; + // The first thread to call get() will invoke the given init function to construct the value. + // Other threads will block until construction completes, then return the same value. + // + // `init` is a functor(typically a lambda) which takes `SpaceFor<T>&` as its parameter and returns + // `Own<T>`. If `init` throws an exception, the exception is propagated out of that thread's + // call to `get()`, and subsequent calls behave as if `get()` hadn't been called at all yet -- + // in other words, subsequent calls retry initialization until it succeeds. + +private: + mutable _::Once once; + mutable SpaceFor<T> space; + mutable Own<T> value; + + template <typename Func> + class InitImpl; +}; + +// ======================================================================================= +// Inline implementation details + +template <typename T> +template <typename... Params> +inline MutexGuarded<T>::MutexGuarded(Params&&... params) + : value(kj::fwd<Params>(params)...) {} + +template <typename T> +inline Locked<T> MutexGuarded<T>::lockExclusive(LockSourceLocationArg location) + const { + mutex.lock(_::Mutex::EXCLUSIVE, nullptr, location); + return Locked<T>(mutex, value); +} + +template <typename T> +inline Locked<const T> MutexGuarded<T>::lockShared(LockSourceLocationArg location) const { + mutex.lock(_::Mutex::SHARED, nullptr, location); + return Locked<const T>(mutex, value); +} + +template <typename T> +inline Maybe<Locked<T>> MutexGuarded<T>::lockExclusiveWithTimeout(Duration timeout, + LockSourceLocationArg location) const { + if (mutex.lock(_::Mutex::EXCLUSIVE, timeout, location)) { + return Locked<T>(mutex, value); + } else { + return nullptr; + } +} + +template <typename T> +inline Maybe<Locked<const T>> MutexGuarded<T>::lockSharedWithTimeout(Duration timeout, + LockSourceLocationArg location) const { + if (mutex.lock(_::Mutex::SHARED, timeout, location)) { + return Locked<const T>(mutex, value); + } else { + return nullptr; + } +} + +template <typename T> +inline const T& MutexGuarded<T>::getAlreadyLockedShared() const { +#ifdef KJ_DEBUG + mutex.assertLockedByCaller(_::Mutex::SHARED); +#endif + return value; +} +template <typename T> +inline T& MutexGuarded<T>::getAlreadyLockedShared() { +#ifdef KJ_DEBUG + mutex.assertLockedByCaller(_::Mutex::SHARED); +#endif + return value; +} +template <typename T> +inline T& MutexGuarded<T>::getAlreadyLockedExclusive() const { +#ifdef KJ_DEBUG + mutex.assertLockedByCaller(_::Mutex::EXCLUSIVE); +#endif + return const_cast<T&>(value); +} + +template <typename T> +template <typename Func> +class Lazy<T>::InitImpl: public _::Once::Initializer { +public: + inline InitImpl(const Lazy<T>& lazy, Func&& func): lazy(lazy), func(kj::fwd<Func>(func)) {} + + void run() override { + lazy.value = func(lazy.space); + } + +private: + const Lazy<T>& lazy; + Func func; +}; + +template <typename T> +template <typename Func> +inline T& Lazy<T>::get(Func&& init, LockSourceLocationArg location) { + if (!once.isInitialized()) { + InitImpl<Func> initImpl(*this, kj::fwd<Func>(init)); + once.runOnce(initImpl, location); + } + return *value; +} + +template <typename T> +template <typename Func> +inline const T& Lazy<T>::get(Func&& init, LockSourceLocationArg location) const { + if (!once.isInitialized()) { + InitImpl<Func> initImpl(*this, kj::fwd<Func>(init)); + once.runOnce(initImpl, location); + } + return *value; +} + +#if KJ_TRACK_LOCK_BLOCKING +struct BlockedOnMutexAcquisition { + const _::Mutex& mutex; + // The mutex we are blocked on. + + const SourceLocation& origin; + // Where did the blocking operation originate from. +}; + +struct BlockedOnCondVarWait { + const _::Mutex& mutex; + // The mutex the condition variable is using (may or may not be locked). + + const void* waiter; + // Pointer to the waiter that's being waited on. + + const SourceLocation& origin; + // Where did the blocking operation originate from. +}; + +struct BlockedOnOnceInit { + const _::Once& once; + + const SourceLocation& origin; + // Where did the blocking operation originate from. +}; + +using BlockedOnReason = OneOf<BlockedOnMutexAcquisition, BlockedOnCondVarWait, BlockedOnOnceInit>; + +Maybe<const BlockedOnReason&> blockedReason() noexcept; +// Returns the information about the reason the current thread is blocked synchronously on KJ +// lock primitives. Returns nullptr if the current thread is not currently blocked on such +// primitives. This is intended to be called from a signal handler to check whether the current +// thread is blocked. Outside of a signal handler there is little value to this function. In those +// cases by definition the thread is not blocked. This includes the callable used as part of a +// condition variable since that happens after the lock is acquired & the current thread is no +// longer blocked). The utility could be made useful for non-signal handler use-cases by being able +// to fetch the pointer to the TLS variable directly (i.e. const BlockedOnReason&*). However, there +// would have to be additional changes/complexity to try make that work since you'd need +// synchronization to ensure that the memory you'd try to reference is still valid. The likely +// solution would be to make these mutually exclusive options where you can use either the fast +// async-safe option, or a mutex-guarded TLS variable you can get a reference to that isn't +// async-safe. That being said, maybe someone can come up with a way to make something that works +// in both use-cases which would of course be more preferable. +#endif + + +} // namespace kj + +KJ_END_HEADER