X-Git-Url: http://plrg.eecs.uci.edu/git/?a=blobdiff_plain;f=folly%2Ftest%2FSynchronizedTestLib-inl.h;h=997069503b15727a94b70a6e7f089ca8390ee961;hb=a60bf0bb374f4b57c0f00cd862e2861e1f381ed0;hp=aebb839b20323923e09fb49d4c1a2a6334a7536a;hpb=27494a20393fa45072e7d526d358835f3abe312a;p=folly.git diff --git a/folly/test/SynchronizedTestLib-inl.h b/folly/test/SynchronizedTestLib-inl.h index aebb839b..99706950 100644 --- a/folly/test/SynchronizedTestLib-inl.h +++ b/folly/test/SynchronizedTestLib-inl.h @@ -1,5 +1,5 @@ /* - * Copyright 2012 Facebook, Inc. + * Copyright 2017 Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,66 +14,472 @@ * limitations under the License. */ -#ifndef FOLLY_TEST_SYNCHRONIZEDTESTLIB_INL_H -#define FOLLY_TEST_SYNCHRONIZEDTESTLIB_INL_H - -#include +#pragma once +#include +#include +#include +#include +#include #include -#include +#include #include +#include +#include #include #include -#include -#include "folly/Foreach.h" -#include "folly/Random.h" -#include "folly/Synchronized.h" +namespace folly { +namespace sync_tests { + +inline std::mt19937& getRNG() { + static const auto seed = folly::randomNumberSeed(); + static std::mt19937 rng(seed); + return rng; +} + +void randomSleep(std::chrono::milliseconds min, std::chrono::milliseconds max) { + std::uniform_int_distribution<> range(min.count(), max.count()); + std::chrono::milliseconds duration(range(getRNG())); + /* sleep override */ + std::this_thread::sleep_for(duration); +} + +/* + * Run a functon simultaneously in a number of different threads. + * + * The function will be passed the index number of the thread it is running in. + * This function makes an attempt to synchronize the start of the threads as + * best as possible. It waits for all threads to be allocated and started + * before invoking the function. + */ +template +void runParallel(size_t numThreads, const Function& function) { + std::vector threads; + threads.reserve(numThreads); + + // Variables used to synchronize all threads to try and start them + // as close to the same time as possible + folly::Synchronized threadsReady(0); + std::condition_variable readyCV; + folly::Synchronized go(false); + std::condition_variable goCV; + + auto worker = [&](size_t threadIndex) { + // Signal that we are ready + ++(*threadsReady.lock()); + readyCV.notify_one(); + + // Wait until we are given the signal to start + // The purpose of this is to try and make sure all threads start + // as close to the same time as possible. + { + auto lockedGo = go.lock(); + goCV.wait(lockedGo.getUniqueLock(), [&] { return *lockedGo; }); + } + + function(threadIndex); + }; + + // Start all of the threads + for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) { + threads.emplace_back([threadIndex, &worker]() { worker(threadIndex); }); + } + + // Wait for all threads to become ready + { + auto readyLocked = threadsReady.lock(); + readyCV.wait(readyLocked.getUniqueLock(), [&] { + return *readyLocked == numThreads; + }); + } + // Now signal the threads that they can go + go = true; + goCV.notify_all(); + + // Wait for all threads to finish + for (auto& thread : threads) { + thread.join(); + } +} + +// testBasic() version for shared lock types +template +typename std::enable_if::is_shared>::type +testBasicImpl() { + folly::Synchronized, Mutex> obj; + const auto& constObj = obj; + + obj.wlock()->resize(1000); + + folly::Synchronized, Mutex> obj2{*obj.wlock()}; + EXPECT_EQ(1000, obj2.rlock()->size()); + + { + auto lockedObj = obj.wlock(); + lockedObj->push_back(10); + EXPECT_EQ(1001, lockedObj->size()); + EXPECT_EQ(10, lockedObj->back()); + EXPECT_EQ(1000, obj2.wlock()->size()); + EXPECT_EQ(1000, obj2.rlock()->size()); + + { + auto unlocker = lockedObj.scopedUnlock(); + EXPECT_EQ(1001, obj.wlock()->size()); + } + } + + { + auto lockedObj = obj.rlock(); + EXPECT_EQ(1001, lockedObj->size()); + EXPECT_EQ(1001, obj.rlock()->size()); + { + auto unlocker = lockedObj.scopedUnlock(); + EXPECT_EQ(1001, obj.wlock()->size()); + } + } + + obj.wlock()->front() = 2; + + { + // contextualLock() on a const reference should grab a shared lock + auto lockedObj = constObj.contextualLock(); + EXPECT_EQ(2, lockedObj->front()); + EXPECT_EQ(2, constObj.rlock()->front()); + EXPECT_EQ(2, obj.rlock()->front()); + } + + EXPECT_EQ(1001, obj.rlock()->size()); + EXPECT_EQ(2, obj.rlock()->front()); + EXPECT_EQ(10, obj.rlock()->back()); + EXPECT_EQ(1000, obj2.rlock()->size()); +} + +// testBasic() version for non-shared lock types +template +typename std::enable_if::is_shared>::type +testBasicImpl() { + folly::Synchronized, Mutex> obj; + const auto& constObj = obj; + + obj.lock()->resize(1000); + + folly::Synchronized, Mutex> obj2{*obj.lock()}; + EXPECT_EQ(1000, obj2.lock()->size()); -static const auto seed = folly::randomNumberSeed(); -typedef std::mt19937 RandomT; -static RandomT rng(seed); + { + auto lockedObj = obj.lock(); + lockedObj->push_back(10); + EXPECT_EQ(1001, lockedObj->size()); + EXPECT_EQ(10, lockedObj->back()); + EXPECT_EQ(1000, obj2.lock()->size()); -template -Integral2 random(Integral1 low, Integral2 up) { - std::uniform_int_distribution<> range(low, up); - return range(rng); + { + auto unlocker = lockedObj.scopedUnlock(); + 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; + + EXPECT_EQ(1001, obj.lock()->size()); + EXPECT_EQ(2, obj.lock()->front()); + EXPECT_EQ(2, obj.contextualLock()->front()); + EXPECT_EQ(10, obj.lock()->back()); + EXPECT_EQ(1000, obj2.lock()->size()); } template void testBasic() { + testBasicImpl(); +} + +// testWithLock() version for shared lock types +template +typename std::enable_if::is_shared>::type +testWithLock() { + folly::Synchronized, Mutex> obj; + const auto& constObj = obj; + + // Test withWLock() and withRLock() + obj.withWLock([](std::vector& lockedObj) { + lockedObj.resize(1000); + lockedObj.push_back(10); + lockedObj.push_back(11); + }); + obj.withWLock([](const std::vector& lockedObj) { + EXPECT_EQ(1002, lockedObj.size()); + }); + constObj.withWLock([](const std::vector& lockedObj) { + EXPECT_EQ(1002, lockedObj.size()); + EXPECT_EQ(11, lockedObj.back()); + }); + obj.withRLock([](const std::vector& lockedObj) { + EXPECT_EQ(1002, lockedObj.size()); + EXPECT_EQ(11, lockedObj.back()); + }); + constObj.withRLock([](const std::vector& 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, 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 +typename std::enable_if::is_shared>::type +testWithLock() { + folly::Synchronized, Mutex> obj; + + // Test withLock() + obj.withLock([](std::vector& lockedObj) { + lockedObj.resize(1000); + lockedObj.push_back(10); + lockedObj.push_back(11); + }); + obj.withLock([](const std::vector& 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, 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()); + }); +} + +template +void testUnlockCommon() { + folly::Synchronized value{7}; + const auto& cv = value; + + { + auto lv = value.contextualLock(); + EXPECT_EQ(7, *lv); + *lv = 5; + lv.unlock(); + EXPECT_TRUE(lv.isNull()); + EXPECT_FALSE(lv); + + auto rlv = cv.contextualLock(); + EXPECT_EQ(5, *rlv); + rlv.unlock(); + EXPECT_TRUE(rlv.isNull()); + EXPECT_FALSE(rlv); + + auto rlv2 = cv.contextualRLock(); + EXPECT_EQ(5, *rlv2); + rlv2.unlock(); + + lv = value.contextualLock(); + EXPECT_EQ(5, *lv); + *lv = 9; + } + + EXPECT_EQ(9, *value.contextualRLock()); +} + +// testUnlock() version for shared lock types +template +typename std::enable_if::is_shared>::type +testUnlock() { + folly::Synchronized value{10}; + { + auto lv = value.wlock(); + EXPECT_EQ(10, *lv); + *lv = 5; + lv.unlock(); + EXPECT_FALSE(lv); + EXPECT_TRUE(lv.isNull()); + + auto rlv = value.rlock(); + EXPECT_EQ(5, *rlv); + rlv.unlock(); + EXPECT_FALSE(rlv); + EXPECT_TRUE(rlv.isNull()); + + auto lv2 = value.wlock(); + EXPECT_EQ(5, *lv2); + *lv2 = 7; + + lv = std::move(lv2); + EXPECT_FALSE(lv2); + EXPECT_TRUE(lv2.isNull()); + EXPECT_FALSE(lv.isNull()); + EXPECT_EQ(7, *lv); + } + + testUnlockCommon(); +} + +// testUnlock() version for non-shared lock types +template +typename std::enable_if::is_shared>::type +testUnlock() { + folly::Synchronized value{10}; + { + auto lv = value.lock(); + EXPECT_EQ(10, *lv); + *lv = 5; + lv.unlock(); + EXPECT_TRUE(lv.isNull()); + EXPECT_FALSE(lv); + + auto lv2 = value.lock(); + EXPECT_EQ(5, *lv2); + *lv2 = 6; + lv2.unlock(); + EXPECT_TRUE(lv2.isNull()); + EXPECT_FALSE(lv2); + + lv = value.lock(); + EXPECT_EQ(6, *lv); + *lv = 7; + + lv2 = std::move(lv); + EXPECT_TRUE(lv.isNull()); + EXPECT_FALSE(lv); + EXPECT_FALSE(lv2.isNull()); + EXPECT_EQ(7, *lv2); + } + + testUnlockCommon(); +} + +// Testing the deprecated SYNCHRONIZED and SYNCHRONIZED_CONST APIs +template +void testDeprecated() { folly::Synchronized, Mutex> obj; obj->resize(1000); auto obj2 = obj; - EXPECT_EQ(obj2->size(), 1000); + EXPECT_EQ(1000, obj2->size()); SYNCHRONIZED (obj) { obj.push_back(10); - EXPECT_EQ(obj.size(), 1001); - EXPECT_EQ(obj.back(), 10); - EXPECT_EQ(obj2->size(), 1000); - - UNSYNCHRONIZED(obj) { - EXPECT_EQ(obj->size(), 1001); - } + EXPECT_EQ(1001, obj.size()); + EXPECT_EQ(10, obj.back()); + EXPECT_EQ(1000, obj2->size()); } SYNCHRONIZED_CONST (obj) { - EXPECT_EQ(obj.size(), 1001); - UNSYNCHRONIZED(obj) { - EXPECT_EQ(obj->size(), 1001); - } + EXPECT_EQ(1001, obj.size()); } SYNCHRONIZED (lockedObj, *&obj) { lockedObj.front() = 2; } - EXPECT_EQ(obj->size(), 1001); - EXPECT_EQ(obj->back(), 10); - EXPECT_EQ(obj2->size(), 1000); + EXPECT_EQ(1001, obj->size()); + EXPECT_EQ(10, obj->back()); + EXPECT_EQ(1000, obj2->size()); EXPECT_EQ(FB_ARG_2_OR_1(1, 2), 2); EXPECT_EQ(FB_ARG_2_OR_1(1), 1); @@ -81,197 +487,368 @@ void testBasic() { template void testConcurrency() { folly::Synchronized, Mutex> v; + static const size_t numThreads = 100; + // Note: I initially tried using itersPerThread = 1000, + // which works fine for most lock types, but std::shared_timed_mutex + // appears to be extraordinarily slow. It could take around 30 seconds + // to run this test with 1000 iterations per thread using shared_timed_mutex. + static const size_t itersPerThread = 100; + + auto pushNumbers = [&](size_t threadIdx) { + // Test lock() + for (size_t n = 0; n < itersPerThread; ++n) { + v.contextualLock()->push_back((itersPerThread * threadIdx) + n); + std::this_thread::yield(); + } + }; + runParallel(numThreads, pushNumbers); - struct Local { - static bool threadMain(int i, - folly::Synchronized, Mutex>& pv) { - usleep(::random(100 * 1000, 1000 * 1000)); + std::vector result; + v.swap(result); - // Test operator-> - pv->push_back(2 * i); + EXPECT_EQ(numThreads * itersPerThread, result.size()); + sort(result.begin(), result.end()); - // Aaand test the SYNCHRONIZED macro - SYNCHRONIZED (v, pv) { - v.push_back(2 * i + 1); - } + for (size_t i = 0; i < itersPerThread * numThreads; ++i) { + EXPECT_EQ(i, result[i]); + } +} - return true; +template +void testAcquireLocked() { + folly::Synchronized, Mutex> v; + folly::Synchronized, Mutex> m; + + auto dualLockWorker = [&](size_t threadIdx) { + // Note: this will be less awkward with C++ 17's structured + // binding functionality, which will make it easier to use the returned + // std::tuple. + if (threadIdx & 1) { + auto ret = acquireLocked(v, m); + std::get<0>(ret)->push_back(threadIdx); + (*std::get<1>(ret))[threadIdx] = threadIdx + 1; + } else { + auto ret = acquireLocked(m, v); + std::get<1>(ret)->push_back(threadIdx); + (*std::get<0>(ret))[threadIdx] = threadIdx + 1; } }; + static const size_t numThreads = 100; + runParallel(numThreads, dualLockWorker); - std::vector results; + std::vector result; + v.swap(result); - static const size_t threads = 100; - FOR_EACH_RANGE (i, 0, threads) { - results.push_back(std::thread([&, i]() { Local::threadMain(i, v); })); - } + EXPECT_EQ(numThreads, result.size()); + sort(result.begin(), result.end()); - FOR_EACH (i, results) { - i->join(); + for (size_t i = 0; i < numThreads; ++i) { + EXPECT_EQ(i, result[i]); } +} + +template +void testAcquireLockedWithConst() { + folly::Synchronized, Mutex> v; + folly::Synchronized, Mutex> m; + + auto dualLockWorker = [&](size_t threadIdx) { + const auto& cm = m; + if (threadIdx & 1) { + auto ret = acquireLocked(v, cm); + (void)std::get<1>(ret)->size(); + std::get<0>(ret)->push_back(threadIdx); + } else { + auto ret = acquireLocked(cm, v); + (void)std::get<0>(ret)->size(); + std::get<1>(ret)->push_back(threadIdx); + } + }; + static const size_t numThreads = 100; + runParallel(numThreads, dualLockWorker); std::vector result; v.swap(result); - EXPECT_EQ(result.size(), 2 * threads); + EXPECT_EQ(numThreads, result.size()); sort(result.begin(), result.end()); - FOR_EACH_RANGE (i, 0, 2 * threads) { - EXPECT_EQ(result[i], i); + for (size_t i = 0; i < numThreads; ++i) { + EXPECT_EQ(i, result[i]); } } +// Testing the deprecated SYNCHRONIZED_DUAL API template void testDualLocking() { folly::Synchronized, Mutex> v; folly::Synchronized, Mutex> m; - struct Local { - static bool threadMain( - int i, - folly::Synchronized, Mutex>& pv, - folly::Synchronized, Mutex>& pm) { - - usleep(::random(100 * 1000, 1000 * 1000)); - - if (i & 1) { - SYNCHRONIZED_DUAL (v, pv, m, pm) { - v.push_back(i); - m[i] = i + 1; - } - } else { - SYNCHRONIZED_DUAL (m, pm, v, pv) { - v.push_back(i); - m[i] = i + 1; - } + auto dualLockWorker = [&](size_t threadIdx) { + if (threadIdx & 1) { + SYNCHRONIZED_DUAL(lv, v, lm, m) { + lv.push_back(threadIdx); + lm[threadIdx] = threadIdx + 1; + } + } else { + SYNCHRONIZED_DUAL(lm, m, lv, v) { + lv.push_back(threadIdx); + lm[threadIdx] = threadIdx + 1; } - - return true; } }; - - std::vector results; - - static const size_t threads = 100; - FOR_EACH_RANGE (i, 0, threads) { - results.push_back( - std::thread([&, i]() { Local::threadMain(i, v, m); })); - } - - FOR_EACH (i, results) { - i->join(); - } + static const size_t numThreads = 100; + runParallel(numThreads, dualLockWorker); std::vector result; v.swap(result); - EXPECT_EQ(result.size(), threads); + EXPECT_EQ(numThreads, result.size()); sort(result.begin(), result.end()); - FOR_EACH_RANGE (i, 0, threads) { - EXPECT_EQ(result[i], i); + for (size_t i = 0; i < numThreads; ++i) { + EXPECT_EQ(i, result[i]); } } +// Testing the deprecated SYNCHRONIZED_DUAL API template void testDualLockingWithConst() { folly::Synchronized, Mutex> v; folly::Synchronized, Mutex> m; - struct Local { - static bool threadMain( - int i, - folly::Synchronized, Mutex>& pv, - const folly::Synchronized, Mutex>& pm) { + auto dualLockWorker = [&](size_t threadIdx) { + const auto& cm = m; + if (threadIdx & 1) { + SYNCHRONIZED_DUAL(lv, v, lm, cm) { + (void)lm.size(); + lv.push_back(threadIdx); + } + } else { + SYNCHRONIZED_DUAL(lm, cm, lv, v) { + (void)lm.size(); + lv.push_back(threadIdx); + } + } + }; + static const size_t numThreads = 100; + runParallel(numThreads, dualLockWorker); - usleep(::random(100 * 1000, 1000 * 1000)); + std::vector result; + v.swap(result); - if (i & 1) { - SYNCHRONIZED_DUAL (v, pv, m, pm) { - size_t s = m.size(); - v.push_back(i); - } - } else { - SYNCHRONIZED_DUAL (m, pm, v, pv) { - size_t s = m.size(); - v.push_back(i); - } + EXPECT_EQ(numThreads, result.size()); + sort(result.begin(), result.end()); + + for (size_t i = 0; i < numThreads; ++i) { + EXPECT_EQ(i, result[i]); + } +} + +template +void testTimed() { + folly::Synchronized, Mutex> v; + folly::Synchronized numTimeouts; + + auto worker = [&](size_t threadIdx) { + // Test directly using operator-> on the lock result + v.contextualLock()->push_back(2 * threadIdx); + + // Test using lock with a timeout + for (;;) { + auto lv = v.contextualLock(std::chrono::milliseconds(5)); + if (!lv) { + ++(*numTimeouts.contextualLock()); + continue; } - return true; + // Sleep for a random time to ensure we trigger timeouts + // in other threads + randomSleep(std::chrono::milliseconds(5), std::chrono::milliseconds(15)); + lv->push_back(2 * threadIdx + 1); + break; } }; - std::vector results; + static const size_t numThreads = 100; + runParallel(numThreads, worker); - static const size_t threads = 100; - FOR_EACH_RANGE (i, 0, threads) { - results.push_back( - std::thread([&, i]() { Local::threadMain(i, v, m); })); - } + std::vector result; + v.swap(result); + + EXPECT_EQ(2 * numThreads, result.size()); + sort(result.begin(), result.end()); - FOR_EACH (i, results) { - i->join(); + for (size_t i = 0; i < 2 * numThreads; ++i) { + EXPECT_EQ(i, result[i]); } + // We generally expect a large number of number timeouts here. + // I'm not adding a check for it since it's theoretically possible that + // we might get 0 timeouts depending on the CPU scheduling if our threads + // don't get to run very often. + LOG(INFO) << "testTimed: " << *numTimeouts.contextualRLock() << " timeouts"; + + // Make sure we can lock with various timeout duration units + { + auto lv = v.contextualLock(std::chrono::milliseconds(5)); + EXPECT_TRUE(bool(lv)); + EXPECT_FALSE(lv.isNull()); + auto lv2 = v.contextualLock(std::chrono::microseconds(5)); + // We may or may not acquire lv2 successfully, depending on whether + // or not this is a recursive mutex type. + } + { + auto lv = v.contextualLock(std::chrono::seconds(1)); + EXPECT_TRUE(bool(lv)); + } +} + +template +void testTimedShared() { + folly::Synchronized, Mutex> v; + folly::Synchronized numTimeouts; + + auto worker = [&](size_t threadIdx) { + // Test directly using operator-> on the lock result + v.wlock()->push_back(threadIdx); + + // Test lock() with a timeout + for (;;) { + auto lv = v.rlock(std::chrono::milliseconds(10)); + if (!lv) { + ++(*numTimeouts.contextualLock()); + continue; + } + + // Sleep while holding the lock. + // + // This will block other threads from acquiring the write lock to add + // their thread index to v, but it won't block threads that have entered + // the for loop and are trying to acquire a read lock. + // + // For lock types that give preference to readers rather than writers, + // this will tend to serialize all threads on the wlock() above. + randomSleep(std::chrono::milliseconds(5), std::chrono::milliseconds(15)); + auto found = std::find(lv->begin(), lv->end(), threadIdx); + CHECK(found != lv->end()); + break; + } + }; + + static const size_t numThreads = 100; + runParallel(numThreads, worker); std::vector result; v.swap(result); - EXPECT_EQ(result.size(), threads); + EXPECT_EQ(numThreads, result.size()); sort(result.begin(), result.end()); - FOR_EACH_RANGE (i, 0, threads) { - EXPECT_EQ(result[i], i); + for (size_t i = 0; i < numThreads; ++i) { + EXPECT_EQ(i, result[i]); } + // We generally expect a small number of timeouts here. + // For locks that give readers preference over writers this should usually + // be 0. With locks that give writers preference we do see a small-ish + // number of read timeouts. + LOG(INFO) << "testTimedShared: " << *numTimeouts.contextualRLock() + << " timeouts"; } +// Testing the deprecated TIMED_SYNCHRONIZED API template void testTimedSynchronized() { folly::Synchronized, Mutex> v; - - struct Local { - static bool threadMain(int i, - folly::Synchronized, Mutex>& pv) { - usleep(::random(100 * 1000, 1000 * 1000)); - - // Test operator-> - pv->push_back(2 * i); - - // Aaand test the TIMED_SYNCHRONIZED macro - for (;;) - TIMED_SYNCHRONIZED (10, v, pv) { - if (v) { - usleep(::random(15 * 1000, 150 * 1000)); - v->push_back(2 * i + 1); - return true; - } - else { - // do nothing - usleep(::random(10 * 1000, 100 * 1000)); - } + folly::Synchronized numTimeouts; + + auto worker = [&](size_t threadIdx) { + // Test operator-> + v->push_back(2 * threadIdx); + + // Aaand test the TIMED_SYNCHRONIZED macro + for (;;) + TIMED_SYNCHRONIZED(5, lv, v) { + if (lv) { + // Sleep for a random time to ensure we trigger timeouts + // in other threads + randomSleep( + std::chrono::milliseconds(5), std::chrono::milliseconds(15)); + lv->push_back(2 * threadIdx + 1); + return; } - return true; - } + ++(*numTimeouts.contextualLock()); + } }; - std::vector results; + static const size_t numThreads = 100; + runParallel(numThreads, worker); - static const size_t threads = 100; - FOR_EACH_RANGE (i, 0, threads) { - results.push_back(std::thread([&, i]() { Local::threadMain(i, v); })); - } + std::vector result; + v.swap(result); + + EXPECT_EQ(2 * numThreads, result.size()); + sort(result.begin(), result.end()); - FOR_EACH (i, results) { - i->join(); + for (size_t i = 0; i < 2 * numThreads; ++i) { + EXPECT_EQ(i, result[i]); } + // We generally expect a large number of number timeouts here. + // I'm not adding a check for it since it's theoretically possible that + // we might get 0 timeouts depending on the CPU scheduling if our threads + // don't get to run very often. + LOG(INFO) << "testTimedSynchronized: " << *numTimeouts.contextualRLock() + << " timeouts"; +} + +// Testing the deprecated TIMED_SYNCHRONIZED_CONST API +template void testTimedSynchronizedWithConst() { + folly::Synchronized, Mutex> v; + folly::Synchronized numTimeouts; + + auto worker = [&](size_t threadIdx) { + // Test operator-> + v->push_back(threadIdx); + + // Test TIMED_SYNCHRONIZED_CONST + for (;;) { + TIMED_SYNCHRONIZED_CONST(10, lv, v) { + if (lv) { + // Sleep while holding the lock. + // + // This will block other threads from acquiring the write lock to add + // their thread index to v, but it won't block threads that have + // entered the for loop and are trying to acquire a read lock. + // + // For lock types that give preference to readers rather than writers, + // this will tend to serialize all threads on the wlock() above. + randomSleep( + std::chrono::milliseconds(5), std::chrono::milliseconds(15)); + auto found = std::find(lv->begin(), lv->end(), threadIdx); + CHECK(found != lv->end()); + return; + } else { + ++(*numTimeouts.contextualLock()); + } + } + } + }; + + static const size_t numThreads = 100; + runParallel(numThreads, worker); std::vector result; v.swap(result); - EXPECT_EQ(result.size(), 2 * threads); + EXPECT_EQ(numThreads, result.size()); sort(result.begin(), result.end()); - FOR_EACH_RANGE (i, 0, 2 * threads) { - EXPECT_EQ(result[i], i); + for (size_t i = 0; i < numThreads; ++i) { + EXPECT_EQ(i, result[i]); } + // We generally expect a small number of timeouts here. + // For locks that give readers preference over writers this should usually + // be 0. With locks that give writers preference we do see a small-ish + // number of read timeouts. + LOG(INFO) << "testTimedSynchronizedWithConst: " + << *numTimeouts.contextualRLock() << " timeouts"; } template void testConstCopy() { @@ -281,11 +858,25 @@ template void testConstCopy() { std::vector result; v.copy(&result); - EXPECT_EQ(result, input); + EXPECT_EQ(input, result); result = v.copy(); - EXPECT_EQ(result, input); + EXPECT_EQ(input, result); } - -#endif /* FOLLY_TEST_SYNCHRONIZEDTESTLIB_INL_H */ +struct NotCopiableNotMovable { + NotCopiableNotMovable(int, const char*) {} + NotCopiableNotMovable(const NotCopiableNotMovable&) = delete; + NotCopiableNotMovable& operator=(const NotCopiableNotMovable&) = delete; + NotCopiableNotMovable(NotCopiableNotMovable&&) = delete; + NotCopiableNotMovable& operator=(NotCopiableNotMovable&&) = delete; +}; + +template void testInPlaceConstruction() { + // This won't compile without construct_in_place + folly::Synchronized a( + folly::construct_in_place, 5, "a" + ); +} +} +}