Commit 719eb094 authored by Kenton Varda's avatar Kenton Varda

Implement mutex and once-init synchronization primitives. Wrap pthreads for…

Implement mutex and once-init synchronization primitives.  Wrap pthreads for now, but plan on raw futexes later.
parent f75b0d93
......@@ -109,7 +109,8 @@ includekj_HEADERS = \
src/kj/debug.h \
src/kj/arena.h \
src/kj/io.h \
src/kj/tuple.h
src/kj/tuple.h \
src/kj/mutex.h
includekjparse_HEADERS = \
src/kj/parse/common.h \
......@@ -146,6 +147,7 @@ libcapnp_la_SOURCES= \
src/kj/debug.c++ \
src/kj/arena.c++ \
src/kj/io.c++ \
src/kj/mutex.c++ \
src/kj/parse/char.c++ \
src/capnp/blob.c++ \
src/capnp/arena.h \
......@@ -195,6 +197,7 @@ capnp_test_SOURCES = \
src/kj/arena-test.c++ \
src/kj/units-test.c++ \
src/kj/tuple-test.c++ \
src/kj/mutex-test.c++ \
src/kj/parse/common-test.c++ \
src/kj/parse/char-test.c++ \
src/capnp/blob-test.c++ \
......
......@@ -201,7 +201,7 @@ kj::ArrayPtr<word> MallocMessageBuilder::allocateSegment(uint minimumSize) {
void* result = calloc(size, sizeof(word));
if (result == nullptr) {
FAIL_SYSCALL("calloc(size, sizeof(word))", ENOMEM, size);
KJ_FAIL_SYSCALL("calloc(size, sizeof(word))", ENOMEM, size);
}
if (!returnedFirstSegment) {
......
......@@ -27,8 +27,6 @@
namespace kj {
const Arena::ArrayDisposerImpl Arena::ArrayDisposerImpl::instance = Arena::ArrayDisposerImpl();
Arena::Arena(size_t chunkSize): state(chunkSize) {}
Arena::Arena(ArrayPtr<byte> scratch, size_t chunkSize): state(chunkSize) {
......@@ -137,13 +135,4 @@ void Arena::setDestructor(void* ptr, void (*destructor)(void*)) {
state.objectList = header;
}
void Arena::ArrayDisposerImpl::disposeImpl(
void* firstElement, size_t elementSize, size_t elementCount,
size_t capacity, void (*destroyElement)(void*)) const {
if (destroyElement != nullptr) {
ExceptionSafeArrayUtil guard(firstElement, elementSize, elementCount, destroyElement);
guard.destroyAll();
}
}
} // namespace kj
......@@ -111,29 +111,11 @@ private:
// Schedule the given destructor to be executed when the Arena is destroyed. `ptr` must be a
// pointer previously returned by an `allocateBytes()` call for which `hasDisposer` was true.
template <typename T>
class DisposerImpl: public Disposer {
public:
static const DisposerImpl instance;
void disposeImpl(void* pointer) const override {
reinterpret_cast<T*>(pointer)->~T();
}
};
class ArrayDisposerImpl: public ArrayDisposer {
public:
static const ArrayDisposerImpl instance;
void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
size_t capacity, void (*destroyElement)(void*)) const override;
};
template <typename T>
static void destroyArray(void* pointer) {
size_t elementCount = *reinterpret_cast<size_t*>(pointer);
constexpr size_t prefixSize = kj::max(alignof(T), sizeof(size_t));
ArrayDisposerImpl::instance.disposeImpl(
DestructorOnlyArrayDisposer::instance.disposeImpl(
reinterpret_cast<byte*>(pointer) + prefixSize,
sizeof(T), elementCount, elementCount, &destroyObject<T>);
}
......@@ -202,7 +184,7 @@ Own<T> Arena::allocateOwn(Params&&... params) {
if (!__has_trivial_constructor(T) || sizeof...(Params) > 0) {
ctor(result, kj::fwd<Params>(params)...);
}
return Own<T>(&result, DisposerImpl<T>::instance);
return Own<T>(&result, DestructorOnlyDisposer<T>::instance);
}
template <typename T>
......@@ -218,12 +200,9 @@ template <typename T>
ArrayBuilder<T> Arena::allocateOwnArrayBuilder(size_t capacity) {
return ArrayBuilder<T>(
reinterpret_cast<T*>(allocateBytes(sizeof(T) * capacity, alignof(T), false)),
capacity, ArrayDisposerImpl::instance);
capacity, DestructorOnlyArrayDisposer::instance);
}
template <typename T>
const Arena::DisposerImpl<T> Arena::DisposerImpl<T>::instance = Arena::DisposerImpl<T>();
} // namespace kj
#endif // KJ_ARENA_H_
......@@ -42,6 +42,18 @@ void ExceptionSafeArrayUtil::destroyAll() {
}
}
const DestructorOnlyArrayDisposer DestructorOnlyArrayDisposer::instance =
DestructorOnlyArrayDisposer();
void DestructorOnlyArrayDisposer::disposeImpl(
void* firstElement, size_t elementSize, size_t elementCount,
size_t capacity, void (*destroyElement)(void*)) const {
if (destroyElement != nullptr) {
ExceptionSafeArrayUtil guard(firstElement, elementSize, elementCount, destroyElement);
guard.destroyAll();
}
}
namespace _ { // private
void* HeapArrayDisposer::allocateImpl(size_t elementSize, size_t elementCount, size_t capacity,
......
......@@ -100,6 +100,14 @@ private:
void (*destroyElement)(void*);
};
class DestructorOnlyArrayDisposer: public ArrayDisposer {
public:
static const DestructorOnlyArrayDisposer instance;
void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
size_t capacity, void (*destroyElement)(void*)) const override;
};
// =======================================================================================
// Array
......@@ -117,7 +125,7 @@ public:
other.ptr = nullptr;
other.size_ = 0;
}
inline Array(Array<RemoveConstOrBogus<T>>&& other) noexcept
inline Array(Array<RemoveConstOrDisable<T>>&& other) noexcept
: ptr(other.ptr), size_(other.size_), disposer(other.disposer) {
other.ptr = nullptr;
other.size_ = 0;
......
......@@ -261,6 +261,10 @@ struct DisallowConstCopyIfNotConst: public DisallowConstCopy {
template <typename T>
struct DisallowConstCopyIfNotConst<const T> {};
template <typename T> struct IsConst_ { static constexpr bool value = false; };
template <typename T> struct IsConst_<const T> { static constexpr bool value = true; };
template <typename T> constexpr bool isConst() { return IsConst_<T>::value; }
template <typename T> struct EnableIfNotConst_ { typedef T Type; };
template <typename T> struct EnableIfNotConst_<const T>;
template <typename T> using EnableIfNotConst = typename EnableIfNotConst_<T>::Type;
......@@ -269,9 +273,9 @@ template <typename T> struct EnableIfConst_;
template <typename T> struct EnableIfConst_<const T> { typedef T Type; };
template <typename T> using EnableIfConst = typename EnableIfConst_<T>::Type;
template <typename T> struct RemoveConstOrBogus_ { struct Type; };
template <typename T> struct RemoveConstOrBogus_<const T> { typedef T Type; };
template <typename T> using RemoveConstOrBogus = typename RemoveConstOrBogus_<T>::Type;
template <typename T> struct RemoveConstOrDisable_ { struct Type; };
template <typename T> struct RemoveConstOrDisable_<const T> { typedef T Type; };
template <typename T> using RemoveConstOrDisable = typename RemoveConstOrDisable_<T>::Type;
template <typename T> struct IsReference_ { static constexpr bool value = false; };
template <typename T> struct IsReference_<T&> { static constexpr bool value = true; };
......
......@@ -134,7 +134,7 @@ namespace kj {
__FILE__, __LINE__, ::kj::Exception::Nature::OS_ERROR, \
_kjSyscallResult.getErrorNumber(), #call, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
#define FAIL_SYSCALL(code, errorNumber, ...) \
#define KJ_FAIL_SYSCALL(code, errorNumber, ...) \
for (::kj::_::Debug::Fault f( \
__FILE__, __LINE__, ::kj::Exception::Nature::OS_ERROR, \
errorNumber, code, #__VA_ARGS__, ##__VA_ARGS__);; f.fatal())
......
......@@ -225,7 +225,7 @@ AutoCloseFd::~AutoCloseFd() noexcept(false) {
unwindDetector.catchExceptionsIfUnwinding([&]() {
// Don't use SYSCALL() here because close() should not be repeated on EINTR.
if (fd >= 0 && close(fd) < 0) {
FAIL_SYSCALL("close", errno, fd) {
KJ_FAIL_SYSCALL("close", errno, fd) {
break;
}
}
......
......@@ -66,6 +66,21 @@ private:
struct Dispose_;
};
template <typename T>
class DestructorOnlyDisposer: public Disposer {
// A disposer that merely calls the type's destructor and nothing else.
public:
static const DestructorOnlyDisposer instance;
void disposeImpl(void* pointer) const override {
reinterpret_cast<T*>(pointer)->~T();
}
};
template <typename T>
const DestructorOnlyDisposer<T> DestructorOnlyDisposer<T>::instance = DestructorOnlyDisposer<T>();
// =======================================================================================
// Own<T> -- An owned pointer.
......@@ -86,10 +101,11 @@ class Own {
// then you've lost any benefit to interoperating with the "standard" unique_ptr.
public:
Own(const Own& other) = delete;
KJ_DISALLOW_COPY(Own);
inline Own(): disposer(nullptr), ptr(nullptr) {}
inline Own(Own&& other) noexcept
: disposer(other.disposer), ptr(other.ptr) { other.ptr = nullptr; }
inline Own(Own<RemoveConstOrBogus<T>>&& other) noexcept
inline Own(Own<RemoveConstOrDisable<T>>&& other) noexcept
: disposer(other.disposer), ptr(other.ptr) { other.ptr = nullptr; }
template <typename U>
inline Own(Own<U>&& other) noexcept
......@@ -124,6 +140,7 @@ private:
T* ptr;
inline explicit Own(decltype(nullptr)): disposer(nullptr), ptr(nullptr) {}
inline bool operator==(decltype(nullptr)) { return ptr == nullptr; }
inline bool operator!=(decltype(nullptr)) { return ptr != nullptr; }
// Only called by Maybe<Own<T>>.
......@@ -244,6 +261,28 @@ Own<Decay<T>> heap(T&& orig) {
return Own<T2>(new T2(kj::fwd<T>(orig)), _::HeapDisposer<T2>::instance);
}
// =======================================================================================
// SpaceFor<T> -- assists in manual allocation
template <typename T>
class SpaceFor {
// A class which has the same size and alignment as T but does not call its constructor or
// destructor automatically. Instead, call construct() to construct a T in the space, which
// returns an Own<T> which will take care of calling T's destructor later.
public:
template <typename... Params>
Own<T> construct(Params&&... params) {
ctor(value, kj::fwd<Params>(params)...);
return Own<T>(&value, DestructorOnlyDisposer<T>::instance);
}
private:
union {
T value;
};
};
// =======================================================================================
// Inline implementation details
......
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "mutex.h"
#include "debug.h"
#include <pthread.h>
#include <unistd.h>
#include <gtest/gtest.h>
namespace kj {
namespace {
// I tried to use std::thread but it threw a pure-virtual exception. It's unclear if it's meant
// to be ready in GCC 4.7.
class Thread {
public:
template <typename Func>
explicit Thread(Func&& func) {
KJ_ASSERT(pthread_create(
&thread, nullptr, &runThread<Decay<Func>>,
new Decay<Func>(kj::fwd<Func>(func))) == 0);
}
~Thread() {
KJ_ASSERT(pthread_join(thread, nullptr) == 0);
}
private:
pthread_t thread;
template <typename Func>
static void* runThread(void* ptr) {
Func* func = reinterpret_cast<Func*>(ptr);
KJ_DEFER(delete func);
(*func)();
return nullptr;
}
};
inline void delay() { usleep(10000); }
TEST(Mutex, MutexGuarded) {
MutexGuarded<uint> value(123);
{
Locked<uint> lock = value.lock();
EXPECT_EQ(123, *lock);
Thread thread([&]() {
Locked<uint> threadLock = value.lock();
EXPECT_EQ(456, *threadLock);
*threadLock = 789;
});
delay();
EXPECT_EQ(123, *lock);
*lock = 456;
auto earlyRelease = kj::mv(lock);
}
EXPECT_EQ(789, *value.lock());
{
auto rlock1 = value.lockForRead();
Thread thread2([&]() {
Locked<uint> threadLock = value.lock();
*threadLock = 321;
});
delay();
EXPECT_EQ(789, *rlock1);
{
auto rlock2 = value.lockForRead();
EXPECT_EQ(789, *rlock2);
auto rlock3 = value.lockForRead();
EXPECT_EQ(789, *rlock3);
auto rlock4 = value.lockForRead();
EXPECT_EQ(789, *rlock4);
}
delay();
EXPECT_EQ(789, *rlock1);
auto earlyRelease = kj::mv(rlock1);
}
EXPECT_EQ(321, *value.lock());
}
TEST(Mutex, Lazy) {
Lazy<uint> lazy;
bool initStarted = false;
Thread thread([&]() {
EXPECT_EQ(123, lazy.get([&](SpaceFor<uint>& space) -> Own<uint> {
__atomic_store_n(&initStarted, true, __ATOMIC_RELAXED);
delay();
return space.construct(123);
}));
});
// Spin until the initializer has been entered in the thread.
while (!__atomic_load_n(&initStarted, __ATOMIC_RELAXED)) {
sched_yield();
}
EXPECT_EQ(123, lazy.get([](SpaceFor<uint>& space) { return space.construct(456); }));
EXPECT_EQ(123, lazy.get([](SpaceFor<uint>& space) { return space.construct(789); }));
}
} // namespace
} // namespace kj
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "mutex.h"
#include "debug.h"
namespace kj {
namespace _ { // private
#define KJ_PTHREAD_CALL(code) \
{ \
int pthreadError = code; \
if (pthreadError != 0) { \
KJ_FAIL_SYSCALL(#code, pthreadError); \
} \
}
#define KJ_PTHREAD_CLEANUP(code) \
{ \
int pthreadError = code; \
if (pthreadError != 0) { \
KJ_LOG(ERROR, #code, strerror(pthreadError)); \
} \
}
Mutex::Mutex() {
KJ_PTHREAD_CALL(pthread_rwlock_init(&mutex, nullptr));
}
Mutex::~Mutex() {
KJ_PTHREAD_CLEANUP(pthread_rwlock_destroy(&mutex));
}
void Mutex::lock() noexcept {
KJ_PTHREAD_CALL(pthread_rwlock_wrlock(&mutex));
}
void Mutex::readLock() noexcept {
KJ_PTHREAD_CALL(pthread_rwlock_rdlock(&mutex));
}
void Mutex::unlock(bool lockedForRead) noexcept {
KJ_PTHREAD_CALL(pthread_rwlock_unlock(&mutex));
}
Once::Once(): initialized(false) {
KJ_PTHREAD_CALL(pthread_mutex_init(&mutex, nullptr));
}
Once::~Once() {
KJ_PTHREAD_CLEANUP(pthread_mutex_destroy(&mutex));
}
void Once::runOnce(Initializer& init) {
KJ_PTHREAD_CALL(pthread_mutex_lock(&mutex));
KJ_DEFER(KJ_PTHREAD_CALL(pthread_mutex_unlock(&mutex)));
if (initialized) {
return;
}
init.run();
__atomic_store_n(&initialized, true, __ATOMIC_RELEASE);
}
} // namespace _ (private)
} // namespace kj
// Copyright (c) 2013, Kenton Varda <temporal@gmail.com>
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef KJ_MUTEX_H_
#define KJ_MUTEX_H_
#include "memory.h"
// For now, we use pthreads.
// TODO(someday): On Linux, use raw futexes. pthreads are bloated with optional features and
// debugging bookkeeping that aren't worth the cost. A mutex should be four bytes, not forty,
// and uncontended operations should be entirely inline!
#include <pthread.h>
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);
void lock() noexcept;
void readLock() noexcept;
void unlock(bool lockedForRead) noexcept;
private:
mutable pthread_rwlock_t mutex;
};
class Once {
// Internal implementation details. See `Lazy<T>`.
public:
Once();
~Once();
KJ_DISALLOW_COPY(Once);
class Initializer {
public:
virtual void run() = 0;
};
void runOnce(Initializer& init);
inline bool isInitialized() noexcept {
// Fast path check to see if runOnce() would simply return immediately.
return __atomic_load_n(&initialized, __ATOMIC_ACQUIRE);
}
private:
bool initialized;
pthread_mutex_t mutex;
};
} // namespace _ (private)
// =======================================================================================
// Public interface
template <typename T>
class Locked {
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>()); }
inline Locked& operator=(Locked&& other) {
if (mutex != nullptr) mutex->unlock(isConst<T>());
mutex = other.mutex;
ptr = other.ptr;
other.mutex = nullptr;
other.ptr = nullptr;
return *this;
}
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. If you think you need recursive locks, you are wrong. Get over it.
public:
template <typename... Params>
explicit MutexGuarded(Params&&... params);
// Initialize the mutex-guarded object by passing the given parameters to its constructor.
Locked<T> lock() const;
// Locks the mutex and returns the guarded object. 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> lockForRead() const;
// Lock the value for read-only access. Multiple read-only locks can be taken concurrently, as
// long as there are no writers.
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 {
public:
template <typename Func>
const T& get(Func&& init);
// 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>::lock() const {
mutex.lock();
return Locked<T>(mutex, value);
}
template <typename T>
inline Locked<const T> MutexGuarded<T>::lockForRead() const {
mutex.readLock();
return Locked<const T>(mutex, 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>
const T& Lazy<T>::get(Func&& init) {
if (!once.isInitialized()) {
InitImpl<Func> initImpl(*this, kj::fwd<Func>(init));
once.runOnce(initImpl);
}
return *value;
}
} // namespace kj
#endif // KJ_MUTEX_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment