blob: b820a7d9bf28192a59fad3e6d47bc831a28b3a0c [file] [log] [blame]
// 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(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(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