// 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 #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 #endif namespace kj { // ======================================================================================= // Private details -- public interfaces follow below. namespace _ { // private class Mutex { // Internal implementation details. See `MutexGuarded`. 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 waitersHead = nullptr; kj::Maybe* 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 in header. #else mutable pthread_rwlock_t mutex; #endif }; class Once { // Internal implementation details. See `Lazy`. 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 in header. #else enum State { UNINITIALIZED, INITIALIZED }; State state; pthread_mutex_t mutex; #endif }; } // namespace _ (private) // ======================================================================================= // Public interface template class Locked { // Return type for `MutexGuarded::lock()`. `Locked` 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() ? _::Mutex::SHARED : _::Mutex::EXCLUSIVE); } inline Locked& operator=(Locked&& other) { if (mutex != nullptr) mutex->unlock(isConst() ? _::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() ? _::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 friend class MutexGuarded; }; template 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 explicit MutexGuarded(Params&&... params); // Initialize the mutex-bounded object by passing the given parameters to its constructor. Locked lockExclusive() const; // Exclusively locks the object and returns it. The returned `Locked` can be passed by // move, similar to `Own`. // // 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 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 auto when(Cond&& condition, Func&& callback) const -> decltype(callback(instance())) { // 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(condition)), value(value) {} }; PredicateImpl impl(kj::fwd(condition), value); mutex.lockWhen(impl); KJ_DEFER(mutex.unlock(_::Mutex::EXCLUSIVE)); return callback(value); } #endif private: mutable _::Mutex mutex; mutable T value; }; template class MutexGuarded { // MutexGuarded cannot guard a const type. This would be pointless anyway, and would complicate // the implementation of Locked, which uses constness to decide what kind of lock it holds. static_assert(sizeof(T) < 0, "MutexGuarded's type cannot be const."); }; template class Lazy { // A lazily-initialized value. public: template T& get(Func&& init); template 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&` as its parameter and returns // `Own`. 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 space; mutable Own value; template class InitImpl; }; // ======================================================================================= // Inline implementation details template template inline MutexGuarded::MutexGuarded(Params&&... params) : value(kj::fwd(params)...) {} template inline Locked MutexGuarded::lockExclusive() const { mutex.lock(_::Mutex::EXCLUSIVE); return Locked(mutex, value); } template inline Locked MutexGuarded::lockShared() const { mutex.lock(_::Mutex::SHARED); return Locked(mutex, value); } template inline const T& MutexGuarded::getAlreadyLockedShared() const { #ifdef KJ_DEBUG mutex.assertLockedByCaller(_::Mutex::SHARED); #endif return value; } template inline T& MutexGuarded::getAlreadyLockedShared() { #ifdef KJ_DEBUG mutex.assertLockedByCaller(_::Mutex::SHARED); #endif return value; } template inline T& MutexGuarded::getAlreadyLockedExclusive() const { #ifdef KJ_DEBUG mutex.assertLockedByCaller(_::Mutex::EXCLUSIVE); #endif return const_cast(value); } template template class Lazy::InitImpl: public _::Once::Initializer { public: inline InitImpl(const Lazy& lazy, Func&& func): lazy(lazy), func(kj::fwd(func)) {} void run() override { lazy.value = func(lazy.space); } private: const Lazy& lazy; Func func; }; template template inline T& Lazy::get(Func&& init) { if (!once.isInitialized()) { InitImpl initImpl(*this, kj::fwd(init)); once.runOnce(initImpl); } return *value; } template template inline const T& Lazy::get(Func&& init) const { if (!once.isInitialized()) { InitImpl initImpl(*this, kj::fwd(init)); once.runOnce(initImpl); } return *value; } } // namespace kj