/* * Copyright 2015-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace folly; using namespace folly::test; using namespace std; using namespace std::chrono; typedef DeterministicSchedule DSched; typedef SharedMutexImpl DSharedMutexReadPriority; typedef SharedMutexImpl DSharedMutexWritePriority; template void runBasicTest() { Lock lock; SharedMutexToken token1; SharedMutexToken token2; SharedMutexToken token3; EXPECT_TRUE(lock.try_lock()); EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock_shared(token1)); lock.unlock(); EXPECT_TRUE(lock.try_lock_shared(token1)); EXPECT_FALSE(lock.try_lock()); EXPECT_TRUE(lock.try_lock_shared(token2)); lock.lock_shared(token3); lock.unlock_shared(token3); lock.unlock_shared(token2); lock.unlock_shared(token1); lock.lock(); lock.unlock(); lock.lock_shared(token1); lock.lock_shared(token2); lock.unlock_shared(token1); lock.unlock_shared(token2); lock.lock(); lock.unlock_and_lock_shared(token1); lock.lock_shared(token2); lock.unlock_shared(token2); lock.unlock_shared(token1); } TEST(SharedMutex, basic) { runBasicTest(); runBasicTest(); runBasicTest(); } template void runBasicHoldersTest() { Lock lock; SharedMutexToken token; { // create an exclusive write lock via holder typename Lock::WriteHolder holder(lock); EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock_shared(token)); // move ownership to another write holder via move constructor typename Lock::WriteHolder holder2(std::move(holder)); EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock_shared(token)); // move ownership to another write holder via assign operator typename Lock::WriteHolder holder3(nullptr); holder3 = std::move(holder2); EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock_shared(token)); // downgrade from exclusive to upgrade lock via move constructor typename Lock::UpgradeHolder holder4(std::move(holder3)); // ensure we can lock from a shared source EXPECT_FALSE(lock.try_lock()); EXPECT_TRUE(lock.try_lock_shared(token)); lock.unlock_shared(token); // promote from upgrade to exclusive lock via move constructor typename Lock::WriteHolder holder5(std::move(holder4)); EXPECT_FALSE(lock.try_lock()); EXPECT_FALSE(lock.try_lock_shared(token)); // downgrade exclusive to shared lock via move constructor typename Lock::ReadHolder holder6(std::move(holder5)); // ensure we can lock from another shared source EXPECT_FALSE(lock.try_lock()); EXPECT_TRUE(lock.try_lock_shared(token)); lock.unlock_shared(token); } { typename Lock::WriteHolder holder(lock); EXPECT_FALSE(lock.try_lock()); } { typename Lock::ReadHolder holder(lock); typename Lock::ReadHolder holder2(lock); typename Lock::UpgradeHolder holder3(lock); } { typename Lock::UpgradeHolder holder(lock); typename Lock::ReadHolder holder2(lock); typename Lock::ReadHolder holder3(std::move(holder)); } } TEST(SharedMutex, basic_holders) { runBasicHoldersTest(); runBasicHoldersTest(); runBasicHoldersTest(); } template void runManyReadLocksTestWithTokens() { Lock lock; vector tokens; for (int i = 0; i < 1000; ++i) { tokens.emplace_back(); EXPECT_TRUE(lock.try_lock_shared(tokens.back())); } for (auto& token : tokens) { lock.unlock_shared(token); } EXPECT_TRUE(lock.try_lock()); lock.unlock(); } TEST(SharedMutex, many_read_locks_with_tokens) { // This test fails in an assertion in the TSAN library because there are too // many mutexes SKIP_IF(folly::kIsSanitizeThread); runManyReadLocksTestWithTokens(); runManyReadLocksTestWithTokens(); runManyReadLocksTestWithTokens(); } template void runManyReadLocksTestWithoutTokens() { Lock lock; for (int i = 0; i < 1000; ++i) { EXPECT_TRUE(lock.try_lock_shared()); } for (int i = 0; i < 1000; ++i) { lock.unlock_shared(); } EXPECT_TRUE(lock.try_lock()); lock.unlock(); } TEST(SharedMutex, many_read_locks_without_tokens) { // This test fails in an assertion in the TSAN library because there are too // many mutexes SKIP_IF(folly::kIsSanitizeThread); runManyReadLocksTestWithoutTokens(); runManyReadLocksTestWithoutTokens(); runManyReadLocksTestWithoutTokens(); } template void runTimeoutInPastTest() { Lock lock; EXPECT_TRUE(lock.try_lock_for(milliseconds(0))); lock.unlock(); EXPECT_TRUE(lock.try_lock_for(milliseconds(-1))); lock.unlock(); EXPECT_TRUE(lock.try_lock_shared_for(milliseconds(0))); lock.unlock_shared(); EXPECT_TRUE(lock.try_lock_shared_for(milliseconds(-1))); lock.unlock_shared(); EXPECT_TRUE(lock.try_lock_until(system_clock::now() - milliseconds(1))); lock.unlock(); EXPECT_TRUE( lock.try_lock_shared_until(system_clock::now() - milliseconds(1))); lock.unlock_shared(); EXPECT_TRUE(lock.try_lock_until(steady_clock::now() - milliseconds(1))); lock.unlock(); EXPECT_TRUE( lock.try_lock_shared_until(steady_clock::now() - milliseconds(1))); lock.unlock_shared(); } TEST(SharedMutex, timeout_in_past) { runTimeoutInPastTest(); runTimeoutInPastTest(); runTimeoutInPastTest(); } template bool funcHasDuration(milliseconds expectedDuration, Func func) { // elapsed time should eventually fall within expectedDuration +- 25% for (int tries = 0; tries < 100; ++tries) { auto start = steady_clock::now(); func(); auto elapsed = steady_clock::now() - start; if (elapsed > expectedDuration - expectedDuration / 4 && elapsed < expectedDuration + expectedDuration / 4) { return true; } } return false; } template void runFailingTryTimeoutTest() { Lock lock; lock.lock(); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE(lock.try_lock_for(milliseconds(10))); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { typename Lock::Token token; EXPECT_FALSE(lock.try_lock_shared_for(milliseconds(10), token)); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE(lock.try_lock_upgrade_for(milliseconds(10))); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE(lock.try_lock_until(steady_clock::now() + milliseconds(10))); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { typename Lock::Token token; EXPECT_FALSE(lock.try_lock_shared_until( steady_clock::now() + milliseconds(10), token)); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE( lock.try_lock_upgrade_until(steady_clock::now() + milliseconds(10))); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE(lock.try_lock_until(system_clock::now() + milliseconds(10))); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { typename Lock::Token token; EXPECT_FALSE(lock.try_lock_shared_until( system_clock::now() + milliseconds(10), token)); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE( lock.try_lock_upgrade_until(system_clock::now() + milliseconds(10))); })); lock.unlock(); lock.lock_shared(); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE(lock.try_lock_for(milliseconds(10))); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE(lock.try_lock_until(steady_clock::now() + milliseconds(10))); })); EXPECT_TRUE(funcHasDuration(milliseconds(10), [&] { EXPECT_FALSE(lock.try_lock_until(system_clock::now() + milliseconds(10))); })); lock.unlock_shared(); lock.lock(); for (int p = 0; p < 8; ++p) { EXPECT_FALSE(lock.try_lock_for(nanoseconds(1 << p))); } lock.unlock(); for (int p = 0; p < 8; ++p) { typename Lock::ReadHolder holder1(lock); typename Lock::ReadHolder holder2(lock); typename Lock::ReadHolder holder3(lock); EXPECT_FALSE(lock.try_lock_for(nanoseconds(1 << p))); } } TEST(SharedMutex, failing_try_timeout) { runFailingTryTimeoutTest(); runFailingTryTimeoutTest(); runFailingTryTimeoutTest(); } template void runBasicUpgradeTest() { Lock lock; typename Lock::Token token1; typename Lock::Token token2; lock.lock_upgrade(); EXPECT_FALSE(lock.try_lock()); EXPECT_TRUE(lock.try_lock_shared(token1)); lock.unlock_shared(token1); lock.unlock_upgrade(); lock.lock_upgrade(); lock.unlock_upgrade_and_lock(); EXPECT_FALSE(lock.try_lock_shared(token1)); lock.unlock(); lock.lock_upgrade(); lock.unlock_upgrade_and_lock_shared(token1); lock.lock_upgrade(); lock.unlock_upgrade_and_lock_shared(token2); lock.unlock_shared(token1); lock.unlock_shared(token2); lock.lock(); lock.unlock_and_lock_upgrade(); EXPECT_TRUE(lock.try_lock_shared(token1)); lock.unlock_upgrade(); lock.unlock_shared(token1); } TEST(SharedMutex, basic_upgrade_tests) { runBasicUpgradeTest(); runBasicUpgradeTest(); runBasicUpgradeTest(); } TEST(SharedMutex, read_has_prio) { SharedMutexReadPriority lock; SharedMutexToken token1; SharedMutexToken token2; lock.lock_shared(token1); bool exclusiveAcquired = false; auto writer = thread([&] { lock.lock(); exclusiveAcquired = true; lock.unlock(); }); // lock() can't complete until we unlock token1, but it should stake // its claim with regards to other exclusive or upgrade locks. We can // use try_lock_upgrade to poll for that eventuality. while (lock.try_lock_upgrade()) { lock.unlock_upgrade(); this_thread::yield(); } EXPECT_FALSE(exclusiveAcquired); // Even though lock() is stuck we should be able to get token2 EXPECT_TRUE(lock.try_lock_shared(token2)); lock.unlock_shared(token1); lock.unlock_shared(token2); writer.join(); EXPECT_TRUE(exclusiveAcquired); } TEST(SharedMutex, write_has_prio) { SharedMutexWritePriority lock; SharedMutexToken token1; SharedMutexToken token2; lock.lock_shared(token1); auto writer = thread([&] { lock.lock(); lock.unlock(); }); // eventually lock() should block readers while (lock.try_lock_shared(token2)) { lock.unlock_shared(token2); this_thread::yield(); } lock.unlock_shared(token1); writer.join(); } struct TokenLocker { SharedMutexToken token; template void lock(T* lockable) { lockable->lock(); } template void unlock(T* lockable) { lockable->unlock(); } template void lock_shared(T* lockable) { lockable->lock_shared(token); } template void unlock_shared(T* lockable) { lockable->unlock_shared(token); } }; struct Locker { template void lock(T* lockable) { lockable->lock(); } template void unlock(T* lockable) { lockable->unlock(); } template void lock_shared(T* lockable) { lockable->lock_shared(); } template void unlock_shared(T* lockable) { lockable->unlock_shared(); } }; struct EnterLocker { template void lock(T* lockable) { lockable->lock(0); } template void unlock(T* lockable) { lockable->unlock(); } template void lock_shared(T* lockable) { lockable->enter(0); } template void unlock_shared(T* lockable) { lockable->leave(); } }; struct PosixRWLock { pthread_rwlock_t lock_; PosixRWLock() { pthread_rwlock_init(&lock_, nullptr); } ~PosixRWLock() { pthread_rwlock_destroy(&lock_); } void lock() { pthread_rwlock_wrlock(&lock_); } void unlock() { pthread_rwlock_unlock(&lock_); } void lock_shared() { pthread_rwlock_rdlock(&lock_); } void unlock_shared() { pthread_rwlock_unlock(&lock_); } }; struct PosixMutex { pthread_mutex_t lock_; PosixMutex() { pthread_mutex_init(&lock_, nullptr); } ~PosixMutex() { pthread_mutex_destroy(&lock_); } void lock() { pthread_mutex_lock(&lock_); } void unlock() { pthread_mutex_unlock(&lock_); } void lock_shared() { pthread_mutex_lock(&lock_); } void unlock_shared() { pthread_mutex_unlock(&lock_); } }; template