Commit 9bcfbd09 authored by Aaryaman Sagar's avatar Aaryaman Sagar Committed by Facebook Github Bot

Add folly::synchronized

Summary:
Some people said the read-lock-when-const behavior of the `SYNCHRONIZED` macro
was desirable, so instead of codemodding the `SYNCHRONIZED` macros out of
existence in favor of `withRLock`, `withWLock` and `withLock`, add a
generalization of the `SYNCHRONIZED()` macros, so they can be removed in favor
of this, which has identical behavior while being more general.

Reviewed By: yfeldblum

Differential Revision: D8248203

fbshipit-source-id: eb3c1444182e42bea79a377a66606d91a6e517bd
parent cd8d5de0
......@@ -32,6 +32,7 @@
#include <folly/Traits.h>
#include <folly/Utility.h>
#include <folly/container/Foreach.h>
#include <folly/functional/ApplyTuple.h>
#include <glog/logging.h>
#include <array>
......@@ -827,86 +828,63 @@ using LockedPtrType = typename std::conditional<
typename SynchronizedType::ConstLockedPtr,
typename SynchronizedType::LockedPtr>::type;
template <typename Synchronized>
class SynchronizedLockerBase {
template <
typename Synchronized,
typename LockFunc,
typename TryLockFunc,
typename... Args>
class SynchronizedLocker {
public:
explicit SynchronizedLockerBase(Synchronized& sync) : synchronized{sync} {}
using LockedPtr = invoke_result_t<LockFunc&, Synchronized&, const Args&...>;
protected:
Synchronized& synchronized;
};
template <typename Synchronized>
class SynchronizedWLocker : public SynchronizedLockerBase<Synchronized> {
public:
using SynchronizedLockerBase<Synchronized>::SynchronizedLockerBase;
using LockedPtr = typename Synchronized::LockedPtr;
auto lock() {
return this->synchronized.wlock();
}
auto tryLock() {
return this->synchronized.tryWLock();
}
};
template <typename Synchronized>
class SynchronizedRLocker : public SynchronizedLockerBase<Synchronized const> {
public:
using SynchronizedLockerBase<Synchronized const>::SynchronizedLockerBase;
using LockedPtr = typename Synchronized::ConstLockedPtr;
template <typename LockFuncType, typename TryLockFuncType, typename... As>
SynchronizedLocker(
Synchronized& sync,
LockFuncType&& lockFunc,
TryLockFuncType tryLockFunc,
As&&... as)
: synchronized{sync},
lockFunc_{std::forward<LockFuncType>(lockFunc)},
tryLockFunc_{std::forward<TryLockFuncType>(tryLockFunc)},
args_{std::forward<As>(as)...} {}
auto lock() {
return this->synchronized.rlock();
auto lock() const {
auto args = std::tuple<const Args&...>{args_};
return apply(lockFunc_, std::tuple_cat(std::tie(synchronized), args));
}
auto tryLock() {
return this->synchronized.tryRLock();
}
};
template <typename Synchronized>
class SynchronizedULocker : public SynchronizedLockerBase<Synchronized> {
public:
using SynchronizedLockerBase<Synchronized>::SynchronizedLockerBase;
using LockedPtr = typename Synchronized::UpgradeLockedPtr;
auto lock() {
return this->synchronized.ulock();
}
auto tryLock() {
return this->synchronized.tryULock();
}
};
template <typename Synchronized>
class SynchronizedLocker : public SynchronizedLockerBase<Synchronized> {
public:
using SynchronizedLockerBase<Synchronized>::SynchronizedLockerBase;
using LockedPtr = typename Synchronized::LockedPtr;
auto lock() {
return this->synchronized.lock();
}
auto tryLock() {
return this->synchronized.tryLock();
}
};
template <typename Lockable>
class LockableLocker {
public:
explicit LockableLocker(Lockable& lockableIn) : lockable{lockableIn} {}
using LockedPtr = std::unique_lock<Lockable>;
auto lock() {
return std::unique_lock<Lockable>{lockable};
}
auto tryLock() {
auto lock = std::unique_lock<Lockable>{lockable, std::defer_lock};
lock.try_lock();
return lock;
auto tryLock() const {
return tryLockFunc_(synchronized);
}
private:
Lockable& lockable;
Synchronized& synchronized;
LockFunc lockFunc_;
TryLockFunc tryLockFunc_;
std::tuple<Args...> args_;
};
template <
typename Synchronized,
typename LockFunc,
typename TryLockFunc,
typename... Args>
auto makeSynchronizedLocker(
Synchronized& synchronized,
LockFunc&& lockFunc,
TryLockFunc&& tryLockFunc,
Args&&... args) {
using LockFuncType = std::decay_t<LockFunc>;
using TryLockFuncType = std::decay_t<TryLockFunc>;
return SynchronizedLocker<
Synchronized,
LockFuncType,
TryLockFuncType,
std::decay_t<Args>...>{synchronized,
std::forward<LockFunc>(lockFunc),
std::forward<TryLockFunc>(tryLockFunc),
std::forward<Args>(args)...};
}
/**
* Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
* manner.
......@@ -924,7 +902,8 @@ class LockableLocker {
* See the benchmarks in folly/test/SynchronizedBenchmark.cpp for more.
*/
template <typename... SynchronizedLocker>
auto /* std::tuple<LockedPtr...> */ lock(SynchronizedLocker... lockersIn) {
auto lock(SynchronizedLocker... lockersIn)
-> std::tuple<typename SynchronizedLocker::LockedPtr...> {
// capture the list of lockers as a tuple
auto lockers = std::forward_as_tuple(lockersIn...);
......@@ -976,6 +955,47 @@ auto /* std::tuple<LockedPtr...> */ lock(SynchronizedLocker... lockersIn) {
}
}
template <typename Synchronized, typename... Args>
auto wlock(Synchronized& synchronized, Args&&... args) {
return detail::makeSynchronizedLocker(
synchronized,
[](auto& s, auto&&... a) {
return s.wlock(std::forward<decltype(a)>(a)...);
},
[](auto& s) { return s.tryWLock(); },
std::forward<Args>(args)...);
}
template <typename Synchronized, typename... Args>
auto rlock(Synchronized& synchronized, Args&&... args) {
return detail::makeSynchronizedLocker(
synchronized,
[](auto& s, auto&&... a) {
return s.rlock(std::forward<decltype(a)>(a)...);
},
[](auto& s) { return s.tryRLock(); },
std::forward<Args>(args)...);
}
template <typename Synchronized, typename... Args>
auto ulock(Synchronized& synchronized, Args&&... args) {
return detail::makeSynchronizedLocker(
synchronized,
[](auto& s, auto&&... a) {
return s.ulock(std::forward<decltype(a)>(a)...);
},
[](auto& s) { return s.tryULock(); },
std::forward<Args>(args)...);
}
template <typename Synchronized, typename... Args>
auto lock(Synchronized& synchronized, Args&&... args) {
return detail::makeSynchronizedLocker(
synchronized,
[](auto& s, auto&&... a) {
return s.lock(std::forward<decltype(a)>(a)...);
},
[](auto& s) { return s.tryLock(); },
std::forward<Args>(args)...);
}
} // namespace detail
/**
......@@ -1494,6 +1514,84 @@ class LockedPtr : public LockedPtrBase<
}
};
/**
* Helper functions that should be passed to either a lock() or synchronized()
* invocation, these return implementation defined structs that will be used
* to lock the synchronized instance appropriately.
*
* lock(wlock(one), rlock(two), wlock(three));
* synchronized([](auto one, two) { ... }, wlock(one), rlock(two));
*
* For example in the above rlock() produces an implementation defined read
* locking helper instance and wlock() a write locking helper
*
* Subsequent arguments passed to these locking helpers, after the first, will
* be passed by const-ref to the corresponding function on the synchronized
* instance. This means that if the function accepts these parameters by
* value, they will be copied. Note that it is not necessary that the primary
* locking function will be invoked at all (for eg. the implementation might
* just invoke the try*Lock() method)
*
* // Try to acquire the lock for one second
* synchronized([](auto) { ... }, wlock(one, 1s));
*
* // The timed lock acquire might never actually be called, if it is not
* // needed by the underlying deadlock avoiding algorithm
* synchronized([](auto, auto) { ... }, rlock(one), wlock(two, 1s));
*
* Note that the arguments passed to to *lock() calls will be passed by
* const-ref to the function invocation, as the implementation might use them
* many times
*/
template <typename D, typename M, typename... Args>
auto wlock(Synchronized<D, M>& synchronized, Args&&... args) {
return detail::wlock(synchronized, std::forward<Args>(args)...);
}
template <typename D, typename M, typename... Args>
auto wlock(const Synchronized<D, M>& synchronized, Args&&... args) {
return detail::wlock(synchronized, std::forward<Args>(args)...);
}
template <typename Data, typename Mutex, typename... Args>
auto rlock(const Synchronized<Data, Mutex>& synchronized, Args&&... args) {
return detail::rlock(synchronized, std::forward<Args>(args)...);
}
template <typename D, typename M, typename... Args>
auto ulock(Synchronized<D, M>& synchronized, Args&&... args) {
return detail::ulock(synchronized, std::forward<Args>(args)...);
}
template <typename D, typename M, typename... Args>
auto ulock(const Synchronized<D, M>& synchronized, Args&&... args) {
return detail::ulock(synchronized, std::forward<Args>(args)...);
}
template <typename D, typename M, typename... Args>
auto lock(Synchronized<D, M>& synchronized, Args&&... args) {
return detail::lock(synchronized, std::forward<Args>(args)...);
}
template <typename D, typename M, typename... Args>
auto lock(const Synchronized<D, M>& synchronized, Args&&... args) {
return detail::lock(synchronized, std::forward<Args>(args)...);
}
/**
* Acquire locks for multiple Synchronized<> objects, in a deadlock-safe
* manner.
*
* Wrap the synchronized instances with the appropriate locking strategy by
* using one of the four strategies - folly::lock (exclusive acquire for
* exclusive only mutexes), folly::rlock (shared acquire for shareable
* mutexes), folly::wlock (exclusive acquire for shareable mutexes) or
* folly::ulock (upgrade acquire for upgrade mutexes) (see above)
*
* The locks will be acquired and the passed callable will be invoked with the
* LockedPtr instances in the order that they were passed to the function
*/
template <typename Func, typename... SynchronizedLockers>
decltype(auto) synchronized(Func&& func, SynchronizedLockers&&... lockers) {
return apply(
std::forward<Func>(func),
lock(std::forward<SynchronizedLockers>(lockers)...));
}
/**
* Acquire locks on many lockables or synchronized instances in such a way
* that the sequence of calls within the function does not cause deadlocks.
......@@ -1528,43 +1626,24 @@ class LockedPtr : public LockedPtrBase<
*/
template <typename LockableOne, typename LockableTwo, typename... Lockables>
void lock(LockableOne& one, LockableTwo& two, Lockables&... lockables) {
auto locks = lock(
detail::LockableLocker<LockableOne>{one},
detail::LockableLocker<LockableTwo>{two},
detail::LockableLocker<Lockables>{lockables}...);
auto locker = [](auto& lockable) {
using Lockable = std::remove_reference_t<decltype(lockable)>;
return detail::makeSynchronizedLocker(
lockable,
[](auto& l) { return std::unique_lock<Lockable>{l}; },
[](auto& l) {
auto lock = std::unique_lock<Lockable>{l, std::defer_lock};
lock.try_lock();
return lock;
});
};
auto locks = lock(locker(one), locker(two), locker(lockables)...);
// release ownership of the locks from the RAII lock wrapper returned by the
// function above
for_each(locks, [&](auto& lock) { lock.release(); });
}
/**
* Helper functions that should be passed to a lock() invocation, these return
* implementation defined structs that lock() will use to lock the
* synchronized instance appropriately.
*
* lock(folly::wlock(one), folly::rlock(two), folly::wlock(three));
*
* For example in the above rlock() produces an implementation defined read
* locking helper instance and wlock() a write locking helper
*/
template <typename Data, typename Mutex>
auto wlock(Synchronized<Data, Mutex>& synchronized) {
return detail::SynchronizedWLocker<Synchronized<Data, Mutex>>{synchronized};
}
template <typename Data, typename Mutex>
auto rlock(Synchronized<Data, Mutex> const& synchronized) {
return detail::SynchronizedRLocker<Synchronized<Data, Mutex>>{synchronized};
}
template <typename Data, typename Mutex>
auto ulock(Synchronized<Data, Mutex>& synchronized) {
return detail::SynchronizedULocker<Synchronized<Data, Mutex>>{synchronized};
}
template <typename Data, typename Mutex>
auto lock(Synchronized<Data, Mutex>& synchronized) {
return detail::SynchronizedLocker<Synchronized<Data, Mutex>>{synchronized};
}
/**
* Acquire locks for multiple Synchronized<T> objects, in a deadlock-safe
* manner.
......
......@@ -632,6 +632,61 @@ class TryLockable {
folly::Function<void()> onUnlock;
};
struct TestSharedMutex {
public:
void lock() {
onLock_();
}
void unlock() {
onUnlock_();
}
void lock_shared() {
onLockShared_();
}
void unlock_shared() {
onUnlockShared_();
}
bool try_lock() {
onLock_();
return true;
}
bool try_lock_shared() {
onLockShared_();
return true;
}
std::function<void()> onLock_;
std::function<void()> onUnlock_;
std::function<void()> onLockShared_;
std::function<void()> onUnlockShared_;
};
struct TestMutex {
public:
void lock() {
onLock();
++numTimesLocked;
}
bool try_lock() {
if (shouldTryLockSucceed) {
lock();
return true;
}
return false;
}
void unlock() {
onUnlock();
++numTimesUnlocked;
}
int numTimesLocked{0};
int numTimesUnlocked{0};
bool shouldTryLockSucceed{true};
std::function<void()> onLock{[] {}};
std::function<void()> onUnlock{[] {}};
};
template <int kLockable, typename Func>
void testTryLock(Func func) {
{
......@@ -850,42 +905,15 @@ TEST(FollyLockTest, TestVariadicLockWithArbitraryLockables) {
EXPECT_TRUE(lckTwo);
}
namespace {
struct TestLock {
public:
void lock() {
onLock();
++numTimesLocked;
}
bool try_lock() {
if (shouldTryLockSucceed) {
lock();
return true;
}
return false;
}
void unlock() {
onUnlock();
++numTimesUnlocked;
}
int numTimesLocked{0};
int numTimesUnlocked{0};
bool shouldTryLockSucceed{true};
std::function<void()> onLock{[] {}};
std::function<void()> onUnlock{[] {}};
};
} // namespace
TEST(FollyLockTest, TestVariadicLockSmartAndPoliteAlgorithm) {
auto one = TestLock{};
auto two = TestLock{};
auto three = TestLock{};
auto one = TestMutex{};
auto two = TestMutex{};
auto three = TestMutex{};
auto makeReset = [&] {
return folly::makeGuard([&] {
one = TestLock{};
two = TestLock{};
three = TestLock{};
one = TestMutex{};
two = TestMutex{};
three = TestMutex{};
});
};
......@@ -945,4 +973,58 @@ TEST(FollyLockTest, TestVariadicLockSmartAndPoliteAlgorithm) {
}
}
TEST(SynchronizedAlgorithmTest, Basic) {
auto sync = Synchronized<int>{0};
auto value = synchronized([](auto s) { return *s; }, wlock(sync));
EXPECT_EQ(value, 0);
}
TEST(SynchronizedAlgorithmTest, BasicNonShareableMutex) {
auto sync = Synchronized<int, std::mutex>{0};
auto value = synchronized([](auto s) { return *s; }, lock(sync));
EXPECT_EQ(value, 0);
}
TEST(Synchronized, SynchronizedFunctionNonConst) {
auto locked = 0;
auto unlocked = 0;
auto sync = Synchronized<int, TestSharedMutex>{
std::piecewise_construct,
std::make_tuple(0),
std::make_tuple([&] { ++locked; }, [&] { ++unlocked; }, [] {}, [] {})};
synchronized([](auto) {}, wlock(sync));
EXPECT_EQ(locked, 1);
EXPECT_EQ(unlocked, 1);
}
TEST(Synchronized, SynchronizedFunctionConst) {
auto locked = 0;
auto unlocked = 0;
auto sync = Synchronized<int, TestSharedMutex>{
std::piecewise_construct,
std::make_tuple(0),
std::make_tuple([] {}, [] {}, [&] { ++locked; }, [&] { ++unlocked; })};
synchronized([](auto) {}, rlock(sync));
EXPECT_EQ(locked, 1);
EXPECT_EQ(unlocked, 1);
}
TEST(Synchronized, SynchronizedFunctionManyObjects) {
auto fail = [] { EXPECT_TRUE(false); };
auto pass = [] {};
auto one = Synchronized<int, TestSharedMutex>{
std::piecewise_construct,
std::make_tuple(0),
std::make_tuple(pass, pass, fail, fail)};
auto two = Synchronized<std::string, TestSharedMutex>{
std::piecewise_construct,
std::make_tuple(),
std::make_tuple(fail, fail, pass, pass)};
synchronized([](auto, auto) {}, wlock(one), rlock(two));
}
} // namespace folly
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