class LockedPtrBase;
template <class LockedType, class LockPolicy>
class LockedPtr;
+template <class LockedType, class LockPolicy = LockPolicyExclusive>
+class LockedGuardPtr;
/**
* SynchronizedBase is a helper parent class for Synchronized<T>.
const std::chrono::duration<Rep, Period>& timeout) const {
return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
}
+
+ /*
+ * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
+ * Once compilers support this, it would be nice to add wguard() and rguard()
+ * methods that return LockedGuardPtr objects.
+ */
+
+ /**
+ * Invoke a function while holding the lock exclusively.
+ *
+ * A reference to the datum will be passed into the function as its only
+ * argument.
+ *
+ * This can be used with a lambda argument for easily defining small critical
+ * sections in the code. For example:
+ *
+ * auto value = obj.withWLock([](auto& data) {
+ * data.doStuff();
+ * return data.getValue();
+ * });
+ */
+ template <class Function>
+ auto withWLock(Function&& function) {
+ LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<Subclass*>(this));
+ return function(*guardPtr);
+ }
+ template <class Function>
+ auto withWLock(Function&& function) const {
+ LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<const Subclass*>(this));
+ return function(*guardPtr);
+ }
+
+ /**
+ * Invoke a function while holding the lock exclusively.
+ *
+ * This is similar to withWLock(), but the function will be passed a
+ * LockedPtr rather than a reference to the data itself.
+ *
+ * This allows scopedUnlock() to be called on the LockedPtr argument if
+ * desired.
+ */
+ template <class Function>
+ auto withWLockPtr(Function&& function) {
+ return function(wlock());
+ }
+ template <class Function>
+ auto withWLockPtr(Function&& function) const {
+ return function(wlock());
+ }
+
+ /**
+ * Invoke a function while holding an the lock in shared mode.
+ *
+ * A const reference to the datum will be passed into the function as its
+ * only argument.
+ */
+ template <class Function>
+ auto withRLock(Function&& function) const {
+ LockedGuardPtr<const Subclass, LockPolicyShared> guardPtr(
+ static_cast<const Subclass*>(this));
+ return function(*guardPtr);
+ }
+
+ template <class Function>
+ auto withRLockPtr(Function&& function) const {
+ return function(rlock());
+ }
};
/**
ConstLockedPtr lock(const std::chrono::duration<Rep, Period>& timeout) const {
return ConstLockedPtr(static_cast<const Subclass*>(this), timeout);
}
+
+ /*
+ * Note: C++ 17 adds guaranteed copy elision. (http://wg21.link/P0135)
+ * Once compilers support this, it would be nice to add guard() methods that
+ * return LockedGuardPtr objects.
+ */
+
+ /**
+ * Invoke a function while holding the lock.
+ *
+ * A reference to the datum will be passed into the function as its only
+ * argument.
+ *
+ * This can be used with a lambda argument for easily defining small critical
+ * sections in the code. For example:
+ *
+ * auto value = obj.withLock([](auto& data) {
+ * data.doStuff();
+ * return data.getValue();
+ * });
+ */
+ template <class Function>
+ auto withLock(Function&& function) {
+ LockedGuardPtr<Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<Subclass*>(this));
+ return function(*guardPtr);
+ }
+ template <class Function>
+ auto withLock(Function&& function) const {
+ LockedGuardPtr<const Subclass, LockPolicyExclusive> guardPtr(
+ static_cast<const Subclass*>(this));
+ return function(*guardPtr);
+ }
+
+ /**
+ * Invoke a function while holding the lock exclusively.
+ *
+ * This is similar to withWLock(), but the function will be passed a
+ * LockedPtr rather than a reference to the data itself.
+ *
+ * This allows scopedUnlock() and getUniqueLock() to be called on the
+ * LockedPtr argument.
+ */
+ template <class Function>
+ auto withLockPtr(Function&& function) {
+ return function(lock());
+ }
+ template <class Function>
+ auto withLockPtr(Function&& function) const {
+ return function(lock());
+ }
};
/**
friend class folly::LockedPtrBase;
template <class LockedType, class LockPolicy>
friend class folly::LockedPtr;
+ template <class LockedType, class LockPolicy>
+ friend class folly::LockedGuardPtr;
/**
* Helper constructors to enable Synchronized for
template <class SynchronizedType, class LockPolicy>
class ScopedUnlocker;
+namespace detail {
+/*
+ * A helper alias that resolves to "const T" if the template parameter
+ * is a const Synchronized<T>, or "T" if the parameter is not const.
+ */
+template <class SynchronizedType>
+using SynchronizedDataType = typename std::conditional<
+ std::is_const<SynchronizedType>::value,
+ typename SynchronizedType::DataType const,
+ typename SynchronizedType::DataType>::type;
+/*
+ * A helper alias that resolves to a ConstLockedPtr if the template parameter
+ * is a const Synchronized<T>, or a LockedPtr if the parameter is not const.
+ */
+template <class SynchronizedType>
+using LockedPtrType = typename std::conditional<
+ std::is_const<SynchronizedType>::value,
+ typename SynchronizedType::ConstLockedPtr,
+ typename SynchronizedType::LockedPtr>::type;
+} // detail
+
/**
* A helper base class for implementing LockedPtr.
*
LockPolicy>;
using UnlockerData = typename Base::UnlockerData;
// CDataType is the DataType with the appropriate const-qualification
- using CDataType = typename std::conditional<
- std::is_const<SynchronizedType>::value,
- typename SynchronizedType::DataType const,
- typename SynchronizedType::DataType>::type;
+ using CDataType = detail::SynchronizedDataType<SynchronizedType>;
public:
using DataType = typename SynchronizedType::DataType;
}
};
-namespace detail {
-/*
- * A helper alias to select a ConstLockedPtr if the template parameter is a
- * const Synchronized<T>, or a LockedPtr if the parameter is not const.
+/**
+ * LockedGuardPtr is a simplified version of LockedPtr.
+ *
+ * It is non-movable, and supports fewer features than LockedPtr. However, it
+ * is ever-so-slightly more performant than LockedPtr. (The destructor can
+ * unconditionally release the lock, without requiring a conditional branch.)
+ *
+ * The relationship between LockedGuardPtr and LockedPtr is similar to that
+ * between std::lock_guard and std::unique_lock.
*/
-template <class SynchronizedType>
-using LockedPtrType = typename std::conditional<
- std::is_const<SynchronizedType>::value,
- typename SynchronizedType::ConstLockedPtr,
- typename SynchronizedType::LockedPtr>::type;
-} // detail
+template <class SynchronizedType, class LockPolicy>
+class LockedGuardPtr {
+ private:
+ // CDataType is the DataType with the appropriate const-qualification
+ using CDataType = detail::SynchronizedDataType<SynchronizedType>;
+
+ public:
+ using DataType = typename SynchronizedType::DataType;
+ using MutexType = typename SynchronizedType::MutexType;
+ using Synchronized = typename std::remove_const<SynchronizedType>::type;
+
+ LockedGuardPtr() = delete;
+
+ /**
+ * Takes a Synchronized<T> and locks it.
+ */
+ explicit LockedGuardPtr(SynchronizedType* parent) : parent_(parent) {
+ LockPolicy::lock(parent_->mutex_);
+ }
+
+ /**
+ * Destructor releases.
+ */
+ ~LockedGuardPtr() {
+ LockPolicy::unlock(parent_->mutex_);
+ }
+
+ /**
+ * Access the locked data.
+ */
+ CDataType* operator->() const {
+ return &parent_->datum_;
+ }
+
+ /**
+ * Access the locked data.
+ */
+ CDataType& operator*() const {
+ return parent_->datum_;
+ }
+
+ private:
+ // This is the entire state of LockedGuardPtr.
+ SynchronizedType* const parent_{nullptr};
+};
/**
* Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
typename std::enable_if<folly::LockTraits<Mutex>::is_shared>::type
testBasicImpl() {
folly::Synchronized<std::vector<int>, Mutex> obj;
+ const auto& constObj = obj;
obj.wlock()->resize(1000);
obj.wlock()->front() = 2;
{
- const auto& constObj = obj;
// contextualLock() on a const reference should grab a shared lock
auto lockedObj = constObj.contextualLock();
EXPECT_EQ(2, lockedObj->front());
typename std::enable_if<!folly::LockTraits<Mutex>::is_shared>::type
testBasicImpl() {
folly::Synchronized<std::vector<int>, Mutex> obj;
+ const auto& constObj = obj;
obj.lock()->resize(1000);
EXPECT_EQ(1001, obj.lock()->size());
}
}
+ {
+ auto lockedObj = constObj.lock();
+ EXPECT_EQ(1001, lockedObj->size());
+ EXPECT_EQ(10, lockedObj->back());
+ EXPECT_EQ(1000, obj2.lock()->size());
+ }
obj.lock()->front() = 2;
testBasicImpl<Mutex>();
}
+// testWithLock() version for shared lock types
+template <class Mutex>
+typename std::enable_if<folly::LockTraits<Mutex>::is_shared>::type
+testWithLock() {
+ folly::Synchronized<std::vector<int>, Mutex> obj;
+ const auto& constObj = obj;
+
+ // Test withWLock() and withRLock()
+ obj.withWLock([](std::vector<int>& lockedObj) {
+ lockedObj.resize(1000);
+ lockedObj.push_back(10);
+ lockedObj.push_back(11);
+ });
+ obj.withWLock([](const std::vector<int>& lockedObj) {
+ EXPECT_EQ(1002, lockedObj.size());
+ });
+ constObj.withWLock([](const std::vector<int>& lockedObj) {
+ EXPECT_EQ(1002, lockedObj.size());
+ EXPECT_EQ(11, lockedObj.back());
+ });
+ obj.withRLock([](const std::vector<int>& lockedObj) {
+ EXPECT_EQ(1002, lockedObj.size());
+ EXPECT_EQ(11, lockedObj.back());
+ });
+ constObj.withRLock([](const std::vector<int>& lockedObj) {
+ EXPECT_EQ(1002, lockedObj.size());
+ });
+
+#if __cpp_generic_lambdas >= 201304
+ obj.withWLock([](auto& lockedObj) { lockedObj.push_back(12); });
+ obj.withWLock(
+ [](const auto& lockedObj) { EXPECT_EQ(1003, lockedObj.size()); });
+ constObj.withWLock([](const auto& lockedObj) {
+ EXPECT_EQ(1003, lockedObj.size());
+ EXPECT_EQ(12, lockedObj.back());
+ });
+ obj.withRLock([](const auto& lockedObj) {
+ EXPECT_EQ(1003, lockedObj.size());
+ EXPECT_EQ(12, lockedObj.back());
+ });
+ constObj.withRLock(
+ [](const auto& lockedObj) { EXPECT_EQ(1003, lockedObj.size()); });
+ obj.withWLock([](auto& lockedObj) { lockedObj.pop_back(); });
+#endif
+
+ // Test withWLockPtr() and withRLockPtr()
+ using SynchType = folly::Synchronized<std::vector<int>, Mutex>;
+#if __cpp_generic_lambdas >= 201304
+ obj.withWLockPtr([](auto&& lockedObj) { lockedObj->push_back(13); });
+ obj.withRLockPtr([](auto&& lockedObj) {
+ EXPECT_EQ(1003, lockedObj->size());
+ EXPECT_EQ(13, lockedObj->back());
+ });
+ constObj.withRLockPtr([](auto&& lockedObj) {
+ EXPECT_EQ(1003, lockedObj->size());
+ EXPECT_EQ(13, lockedObj->back());
+ });
+ obj.withWLockPtr([&](auto&& lockedObj) {
+ lockedObj->push_back(14);
+ {
+ auto unlocker = lockedObj.scopedUnlock();
+ obj.wlock()->push_back(15);
+ }
+ EXPECT_EQ(15, lockedObj->back());
+ });
+ constObj.withWLockPtr([](auto&& lockedObj) {
+ EXPECT_EQ(1005, lockedObj->size());
+ EXPECT_EQ(15, lockedObj->back());
+ });
+#else
+ obj.withWLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
+ lockedObj->push_back(13);
+ lockedObj->push_back(14);
+ lockedObj->push_back(15);
+ });
+#endif
+
+ obj.withWLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
+ lockedObj->push_back(16);
+ EXPECT_EQ(1006, lockedObj->size());
+ });
+ constObj.withWLockPtr([](typename SynchType::ConstWLockedPtr&& lockedObj) {
+ EXPECT_EQ(1006, lockedObj->size());
+ EXPECT_EQ(16, lockedObj->back());
+ });
+ obj.withRLockPtr([](typename SynchType::ConstLockedPtr&& lockedObj) {
+ EXPECT_EQ(1006, lockedObj->size());
+ EXPECT_EQ(16, lockedObj->back());
+ });
+ constObj.withRLockPtr([](typename SynchType::ConstLockedPtr&& lockedObj) {
+ EXPECT_EQ(1006, lockedObj->size());
+ EXPECT_EQ(16, lockedObj->back());
+ });
+}
+
+// testWithLock() version for non-shared lock types
+template <class Mutex>
+typename std::enable_if<!folly::LockTraits<Mutex>::is_shared>::type
+testWithLock() {
+ folly::Synchronized<std::vector<int>, Mutex> obj;
+
+ // Test withLock()
+ obj.withLock([](std::vector<int>& lockedObj) {
+ lockedObj.resize(1000);
+ lockedObj.push_back(10);
+ lockedObj.push_back(11);
+ });
+ obj.withLock([](const std::vector<int>& lockedObj) {
+ EXPECT_EQ(1002, lockedObj.size());
+ });
+
+#if __cpp_generic_lambdas >= 201304
+ obj.withLock([](auto& lockedObj) { lockedObj.push_back(12); });
+ obj.withLock(
+ [](const auto& lockedObj) { EXPECT_EQ(1003, lockedObj.size()); });
+ obj.withLock([](auto& lockedObj) { lockedObj.pop_back(); });
+#endif
+
+ // Test withLockPtr()
+ using SynchType = folly::Synchronized<std::vector<int>, Mutex>;
+#if __cpp_generic_lambdas >= 201304
+ obj.withLockPtr([](auto&& lockedObj) { lockedObj->push_back(13); });
+ obj.withLockPtr([](auto&& lockedObj) {
+ EXPECT_EQ(1003, lockedObj->size());
+ EXPECT_EQ(13, lockedObj->back());
+ });
+ obj.withLockPtr([&](auto&& lockedObj) {
+ lockedObj->push_back(14);
+ {
+ auto unlocker = lockedObj.scopedUnlock();
+ obj.lock()->push_back(15);
+ }
+ EXPECT_EQ(1005, lockedObj->size());
+ EXPECT_EQ(15, lockedObj->back());
+ });
+#else
+ obj.withLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
+ lockedObj->push_back(13);
+ lockedObj->push_back(14);
+ lockedObj->push_back(15);
+ });
+#endif
+
+ obj.withLockPtr([](typename SynchType::LockedPtr&& lockedObj) {
+ lockedObj->push_back(16);
+ EXPECT_EQ(1006, lockedObj->size());
+ });
+ const auto& constObj = obj;
+ constObj.withLockPtr([](typename SynchType::ConstLockedPtr&& lockedObj) {
+ EXPECT_EQ(1006, lockedObj->size());
+ EXPECT_EQ(16, lockedObj->back());
+ });
+}
+
// Testing the deprecated SYNCHRONIZED and SYNCHRONIZED_CONST APIs
template <class Mutex>
void testDeprecated() {