Mercurial > hg > sv-dependency-builds
diff osx/include/kj/mutex.h @ 49:3ab5a40c4e3b
Add Capnp and KJ builds for OSX
author | Chris Cannam <cannam@all-day-breakfast.com> |
---|---|
date | Tue, 25 Oct 2016 14:48:23 +0100 |
parents | |
children | 0994c39f1e94 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/osx/include/kj/mutex.h Tue Oct 25 14:48:23 2016 +0100 @@ -0,0 +1,369 @@ +// 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. + +#ifndef KJ_MUTEX_H_ +#define KJ_MUTEX_H_ + +#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. + +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; + +#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 guarded 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, guarded 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-guarded 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. + +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 + +#endif // KJ_MUTEX_H_