415 lines
13 KiB
C++
415 lines
13 KiB
C++
// 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
|
|
|
|
#if defined(__GNUC__) && !KJ_HEADER_WARNINGS
|
|
#pragma GCC system_header
|
|
#endif
|
|
|
|
#include "memory.h"
|
|
#include <inttypes.h>
|
|
|
|
#if __linux__ && !defined(KJ_USE_FUTEX)
|
|
#define KJ_USE_FUTEX 1
|
|
#endif
|
|
|
|
#if !KJ_USE_FUTEX && !_WIN32
|
|
// On Linux we use futex. On other platforms we wrap pthreads.
|
|
// TODO(someday): Write efficient low-level locking primitives for other platforms.
|
|
#include <pthread.h>
|
|
#endif
|
|
|
|
namespace kj {
|
|
|
|
// =======================================================================================
|
|
// Private details -- public interfaces follow below.
|
|
|
|
namespace _ { // private
|
|
|
|
class Mutex {
|
|
// Internal implementation details. See `MutexGuarded<T>`.
|
|
|
|
public:
|
|
Mutex();
|
|
~Mutex();
|
|
KJ_DISALLOW_COPY(Mutex);
|
|
|
|
enum Exclusivity {
|
|
EXCLUSIVE,
|
|
SHARED
|
|
};
|
|
|
|
void lock(Exclusivity exclusivity);
|
|
void unlock(Exclusivity exclusivity);
|
|
|
|
void assertLockedByCaller(Exclusivity exclusivity);
|
|
// 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.
|
|
|
|
#if KJ_USE_FUTEX // TODO(someday): Implement on pthread & win32
|
|
class Predicate {
|
|
public:
|
|
virtual bool check() = 0;
|
|
};
|
|
|
|
void lockWhen(Predicate& predicate);
|
|
// Lock (exclusively) when predicate.check() returns true.
|
|
#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.
|
|
|
|
static constexpr uint EXCLUSIVE_HELD = 1u << 31;
|
|
static constexpr uint EXCLUSIVE_REQUESTED = 1u << 30;
|
|
static constexpr uint SHARED_COUNT_MASK = EXCLUSIVE_REQUESTED - 1;
|
|
|
|
struct Waiter;
|
|
kj::Maybe<Waiter&> waitersHead = nullptr;
|
|
kj::Maybe<Waiter&>* waitersTail = &waitersHead;
|
|
// linked list of waitUntil()s; can only modify under lock
|
|
|
|
#elif _WIN32
|
|
uintptr_t srwLock; // Actually an SRWLOCK, but don't want to #include <windows.h> in header.
|
|
|
|
#else
|
|
mutable pthread_rwlock_t mutex;
|
|
#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);
|
|
|
|
#if _WIN32 // 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
|
|
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; }
|
|
|
|
private:
|
|
_::Mutex* mutex;
|
|
T* ptr;
|
|
|
|
inline Locked(_::Mutex& mutex, T& value): mutex(&mutex), ptr(&value) {}
|
|
|
|
template <typename U>
|
|
friend class MutexGuarded;
|
|
};
|
|
|
|
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() 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() 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.
|
|
|
|
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.
|
|
|
|
#if KJ_USE_FUTEX // TODO(someday): Implement on pthread & win32
|
|
template <typename Cond, typename Func>
|
|
auto when(Cond&& condition, Func&& callback) 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` returns 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).
|
|
|
|
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), value);
|
|
mutex.lockWhen(impl);
|
|
KJ_DEFER(mutex.unlock(_::Mutex::EXCLUSIVE));
|
|
return callback(value);
|
|
}
|
|
#endif
|
|
|
|
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 Lazy {
|
|
// A lazily-initialized value.
|
|
|
|
public:
|
|
template <typename Func>
|
|
T& get(Func&& init);
|
|
template <typename Func>
|
|
const T& get(Func&& init) 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() const {
|
|
mutex.lock(_::Mutex::EXCLUSIVE);
|
|
return Locked<T>(mutex, value);
|
|
}
|
|
|
|
template <typename T>
|
|
inline Locked<const T> MutexGuarded<T>::lockShared() const {
|
|
mutex.lock(_::Mutex::SHARED);
|
|
return Locked<const T>(mutex, value);
|
|
}
|
|
|
|
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) {
|
|
if (!once.isInitialized()) {
|
|
InitImpl<Func> initImpl(*this, kj::fwd<Func>(init));
|
|
once.runOnce(initImpl);
|
|
}
|
|
return *value;
|
|
}
|
|
|
|
template <typename T>
|
|
template <typename Func>
|
|
inline const T& Lazy<T>::get(Func&& init) const {
|
|
if (!once.isInitialized()) {
|
|
InitImpl<Func> initImpl(*this, kj::fwd<Func>(init));
|
|
once.runOnce(initImpl);
|
|
}
|
|
return *value;
|
|
}
|
|
|
|
} // namespace kj
|