// Copyright 2017 The Abseil Authors. // // 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 // // https://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 "absl/synchronization/mutex.h" #ifdef _WIN32 #include <windows.h> #endif #include <algorithm> #include <atomic> #include <cstdlib> #include <functional> #include <memory> #include <random> #include <string> #include <thread> // NOLINT(build/c++11) #include <vector> #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/sysinfo.h" #include "absl/memory/memory.h" #include "absl/synchronization/internal/thread_pool.h" #include "absl/time/clock.h" #include "absl/time/time.h" namespace { // TODO(dmauro): Replace with a commandline flag. static constexpr bool kExtendedTest = false; std::unique_ptr<absl::synchronization_internal::ThreadPool> CreatePool( int threads) { return absl::make_unique<absl::synchronization_internal::ThreadPool>(threads); } std::unique_ptr<absl::synchronization_internal::ThreadPool> CreateDefaultPool() { return CreatePool(kExtendedTest ? 32 : 10); } // Hack to schedule a function to run on a thread pool thread after a // duration has elapsed. static void ScheduleAfter(absl::synchronization_internal::ThreadPool *tp, absl::Duration after, const std::function<void()> &func) { tp->Schedule([func, after] { absl::SleepFor(after); func(); }); } struct TestContext { int iterations; int threads; int g0; // global 0 int g1; // global 1 absl::Mutex mu; absl::CondVar cv; }; // To test whether the invariant check call occurs static std::atomic<bool> invariant_checked; static bool GetInvariantChecked() { return invariant_checked.load(std::memory_order_relaxed); } static void SetInvariantChecked(bool new_value) { invariant_checked.store(new_value, std::memory_order_relaxed); } static void CheckSumG0G1(void *v) { TestContext *cxt = static_cast<TestContext *>(v); ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in CheckSumG0G1"); SetInvariantChecked(true); } static void TestMu(TestContext *cxt, int c) { for (int i = 0; i != cxt->iterations; i++) { absl::MutexLock l(&cxt->mu); int a = cxt->g0 + 1; cxt->g0 = a; cxt->g1--; } } static void TestTry(TestContext *cxt, int c) { for (int i = 0; i != cxt->iterations; i++) { do { std::this_thread::yield(); } while (!cxt->mu.TryLock()); int a = cxt->g0 + 1; cxt->g0 = a; cxt->g1--; cxt->mu.Unlock(); } } static void TestR20ms(TestContext *cxt, int c) { for (int i = 0; i != cxt->iterations; i++) { absl::ReaderMutexLock l(&cxt->mu); absl::SleepFor(absl::Milliseconds(20)); cxt->mu.AssertReaderHeld(); } } static void TestRW(TestContext *cxt, int c) { if ((c & 1) == 0) { for (int i = 0; i != cxt->iterations; i++) { absl::WriterMutexLock l(&cxt->mu); cxt->g0++; cxt->g1--; cxt->mu.AssertHeld(); cxt->mu.AssertReaderHeld(); } } else { for (int i = 0; i != cxt->iterations; i++) { absl::ReaderMutexLock l(&cxt->mu); ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in TestRW"); cxt->mu.AssertReaderHeld(); } } } struct MyContext { int target; TestContext *cxt; bool MyTurn(); }; bool MyContext::MyTurn() { TestContext *cxt = this->cxt; return cxt->g0 == this->target || cxt->g0 == cxt->iterations; } static void TestAwait(TestContext *cxt, int c) { MyContext mc; mc.target = c; mc.cxt = cxt; absl::MutexLock l(&cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { cxt->mu.Await(absl::Condition(&mc, &MyContext::MyTurn)); ABSL_RAW_CHECK(mc.MyTurn(), "Error in TestAwait"); cxt->mu.AssertHeld(); if (cxt->g0 < cxt->iterations) { int a = cxt->g0 + 1; cxt->g0 = a; mc.target += cxt->threads; } } } static void TestSignalAll(TestContext *cxt, int c) { int target = c; absl::MutexLock l(&cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { while (cxt->g0 != target && cxt->g0 != cxt->iterations) { cxt->cv.Wait(&cxt->mu); } if (cxt->g0 < cxt->iterations) { int a = cxt->g0 + 1; cxt->g0 = a; cxt->cv.SignalAll(); target += cxt->threads; } } } static void TestSignal(TestContext *cxt, int c) { ABSL_RAW_CHECK(cxt->threads == 2, "TestSignal should use 2 threads"); int target = c; absl::MutexLock l(&cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { while (cxt->g0 != target && cxt->g0 != cxt->iterations) { cxt->cv.Wait(&cxt->mu); } if (cxt->g0 < cxt->iterations) { int a = cxt->g0 + 1; cxt->g0 = a; cxt->cv.Signal(); target += cxt->threads; } } } static void TestCVTimeout(TestContext *cxt, int c) { int target = c; absl::MutexLock l(&cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { while (cxt->g0 != target && cxt->g0 != cxt->iterations) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(100)); } if (cxt->g0 < cxt->iterations) { int a = cxt->g0 + 1; cxt->g0 = a; cxt->cv.SignalAll(); target += cxt->threads; } } } static bool G0GE2(TestContext *cxt) { return cxt->g0 >= 2; } static void TestTime(TestContext *cxt, int c, bool use_cv) { ABSL_RAW_CHECK(cxt->iterations == 1, "TestTime should only use 1 iteration"); ABSL_RAW_CHECK(cxt->threads > 2, "TestTime should use more than 2 threads"); const bool kFalse = false; absl::Condition false_cond(&kFalse); absl::Condition g0ge2(G0GE2, cxt); if (c == 0) { absl::MutexLock l(&cxt->mu); absl::Time start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), "TestTime failed"); } absl::Duration elapsed = absl::Now() - start; ABSL_RAW_CHECK( absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), "TestTime failed"); ABSL_RAW_CHECK(cxt->g0 == 1, "TestTime failed"); start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), "TestTime failed"); } elapsed = absl::Now() - start; ABSL_RAW_CHECK( absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), "TestTime failed"); cxt->g0++; if (use_cv) { cxt->cv.Signal(); } start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(4)); } else { ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(4)), "TestTime failed"); } elapsed = absl::Now() - start; ABSL_RAW_CHECK( absl::Seconds(3.9) <= elapsed && elapsed <= absl::Seconds(6.0), "TestTime failed"); ABSL_RAW_CHECK(cxt->g0 >= 3, "TestTime failed"); start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), "TestTime failed"); } elapsed = absl::Now() - start; ABSL_RAW_CHECK( absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), "TestTime failed"); if (use_cv) { cxt->cv.SignalAll(); } start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), "TestTime failed"); } elapsed = absl::Now() - start; ABSL_RAW_CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), "TestTime failed"); ABSL_RAW_CHECK(cxt->g0 == cxt->threads, "TestTime failed"); } else if (c == 1) { absl::MutexLock l(&cxt->mu); const absl::Time start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Milliseconds(500)); } else { ABSL_RAW_CHECK( !cxt->mu.AwaitWithTimeout(false_cond, absl::Milliseconds(500)), "TestTime failed"); } const absl::Duration elapsed = absl::Now() - start; ABSL_RAW_CHECK( absl::Seconds(0.4) <= elapsed && elapsed <= absl::Seconds(0.9), "TestTime failed"); cxt->g0++; } else if (c == 2) { absl::MutexLock l(&cxt->mu); if (use_cv) { while (cxt->g0 < 2) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(100)); } } else { ABSL_RAW_CHECK(cxt->mu.AwaitWithTimeout(g0ge2, absl::Seconds(100)), "TestTime failed"); } cxt->g0++; } else { absl::MutexLock l(&cxt->mu); if (use_cv) { while (cxt->g0 < 2) { cxt->cv.Wait(&cxt->mu); } } else { cxt->mu.Await(g0ge2); } cxt->g0++; } } static void TestMuTime(TestContext *cxt, int c) { TestTime(cxt, c, false); } static void TestCVTime(TestContext *cxt, int c) { TestTime(cxt, c, true); } static void EndTest(int *c0, int *c1, absl::Mutex *mu, absl::CondVar *cv, const std::function<void(int)>& cb) { mu->Lock(); int c = (*c0)++; mu->Unlock(); cb(c); absl::MutexLock l(mu); (*c1)++; cv->Signal(); } // Code common to RunTest() and RunTestWithInvariantDebugging(). static int RunTestCommon(TestContext *cxt, void (*test)(TestContext *cxt, int), int threads, int iterations, int operations) { absl::Mutex mu2; absl::CondVar cv2; int c0 = 0; int c1 = 0; cxt->g0 = 0; cxt->g1 = 0; cxt->iterations = iterations; cxt->threads = threads; absl::synchronization_internal::ThreadPool tp(threads); for (int i = 0; i != threads; i++) { tp.Schedule(std::bind(&EndTest, &c0, &c1, &mu2, &cv2, std::function<void(int)>( std::bind(test, cxt, std::placeholders::_1)))); } mu2.Lock(); while (c1 != threads) { cv2.Wait(&mu2); } mu2.Unlock(); return cxt->g0; } // Basis for the parameterized tests configured below. static int RunTest(void (*test)(TestContext *cxt, int), int threads, int iterations, int operations) { TestContext cxt; return RunTestCommon(&cxt, test, threads, iterations, operations); } // Like RunTest(), but sets an invariant on the tested Mutex and // verifies that the invariant check happened. The invariant function // will be passed the TestContext* as its arg and must call // SetInvariantChecked(true); #if !defined(ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED) static int RunTestWithInvariantDebugging(void (*test)(TestContext *cxt, int), int threads, int iterations, int operations, void (*invariant)(void *)) { absl::EnableMutexInvariantDebugging(true); SetInvariantChecked(false); TestContext cxt; cxt.mu.EnableInvariantDebugging(invariant, &cxt); int ret = RunTestCommon(&cxt, test, threads, iterations, operations); ABSL_RAW_CHECK(GetInvariantChecked(), "Invariant not checked"); absl::EnableMutexInvariantDebugging(false); // Restore. return ret; } #endif // -------------------------------------------------------- // Test for fix of bug in TryRemove() struct TimeoutBugStruct { absl::Mutex mu; bool a; int a_waiter_count; }; static void WaitForA(TimeoutBugStruct *x) { x->mu.LockWhen(absl::Condition(&x->a)); x->a_waiter_count--; x->mu.Unlock(); } static bool NoAWaiters(TimeoutBugStruct *x) { return x->a_waiter_count == 0; } // Test that a CondVar.Wait(&mutex) can un-block a call to mutex.Await() in // another thread. TEST(Mutex, CondVarWaitSignalsAwait) { // Use a struct so the lock annotations apply. struct { absl::Mutex barrier_mu; bool barrier ABSL_GUARDED_BY(barrier_mu) = false; absl::Mutex release_mu; bool release ABSL_GUARDED_BY(release_mu) = false; absl::CondVar released_cv; } state; auto pool = CreateDefaultPool(); // Thread A. Sets barrier, waits for release using Mutex::Await, then // signals released_cv. pool->Schedule([&state] { state.release_mu.Lock(); state.barrier_mu.Lock(); state.barrier = true; state.barrier_mu.Unlock(); state.release_mu.Await(absl::Condition(&state.release)); state.released_cv.Signal(); state.release_mu.Unlock(); }); state.barrier_mu.LockWhen(absl::Condition(&state.barrier)); state.barrier_mu.Unlock(); state.release_mu.Lock(); // Thread A is now blocked on release by way of Mutex::Await(). // Set release. Calling released_cv.Wait() should un-block thread A, // which will signal released_cv. If not, the test will hang. state.release = true; state.released_cv.Wait(&state.release_mu); state.release_mu.Unlock(); } // Test that a CondVar.WaitWithTimeout(&mutex) can un-block a call to // mutex.Await() in another thread. TEST(Mutex, CondVarWaitWithTimeoutSignalsAwait) { // Use a struct so the lock annotations apply. struct { absl::Mutex barrier_mu; bool barrier ABSL_GUARDED_BY(barrier_mu) = false; absl::Mutex release_mu; bool release ABSL_GUARDED_BY(release_mu) = false; absl::CondVar released_cv; } state; auto pool = CreateDefaultPool(); // Thread A. Sets barrier, waits for release using Mutex::Await, then // signals released_cv. pool->Schedule([&state] { state.release_mu.Lock(); state.barrier_mu.Lock(); state.barrier = true; state.barrier_mu.Unlock(); state.release_mu.Await(absl::Condition(&state.release)); state.released_cv.Signal(); state.release_mu.Unlock(); }); state.barrier_mu.LockWhen(absl::Condition(&state.barrier)); state.barrier_mu.Unlock(); state.release_mu.Lock(); // Thread A is now blocked on release by way of Mutex::Await(). // Set release. Calling released_cv.Wait() should un-block thread A, // which will signal released_cv. If not, the test will hang. state.release = true; EXPECT_TRUE( !state.released_cv.WaitWithTimeout(&state.release_mu, absl::Seconds(10))) << "; Unrecoverable test failure: CondVar::WaitWithTimeout did not " "unblock the absl::Mutex::Await call in another thread."; state.release_mu.Unlock(); } // Test for regression of a bug in loop of TryRemove() TEST(Mutex, MutexTimeoutBug) { auto tp = CreateDefaultPool(); TimeoutBugStruct x; x.a = false; x.a_waiter_count = 2; tp->Schedule(std::bind(&WaitForA, &x)); tp->Schedule(std::bind(&WaitForA, &x)); absl::SleepFor(absl::Seconds(1)); // Allow first two threads to hang. // The skip field of the second will point to the first because there are // only two. // Now cause a thread waiting on an always-false to time out // This would deadlock when the bug was present. bool always_false = false; x.mu.LockWhenWithTimeout(absl::Condition(&always_false), absl::Milliseconds(500)); // if we get here, the bug is not present. Cleanup the state. x.a = true; // wakeup the two waiters on A x.mu.Await(absl::Condition(&NoAWaiters, &x)); // wait for them to exit x.mu.Unlock(); } struct CondVarWaitDeadlock : testing::TestWithParam<int> { absl::Mutex mu; absl::CondVar cv; bool cond1 = false; bool cond2 = false; bool read_lock1; bool read_lock2; bool signal_unlocked; CondVarWaitDeadlock() { read_lock1 = GetParam() & (1 << 0); read_lock2 = GetParam() & (1 << 1); signal_unlocked = GetParam() & (1 << 2); } void Waiter1() { if (read_lock1) { mu.ReaderLock(); while (!cond1) { cv.Wait(&mu); } mu.ReaderUnlock(); } else { mu.Lock(); while (!cond1) { cv.Wait(&mu); } mu.Unlock(); } } void Waiter2() { if (read_lock2) { mu.ReaderLockWhen(absl::Condition(&cond2)); mu.ReaderUnlock(); } else { mu.LockWhen(absl::Condition(&cond2)); mu.Unlock(); } } }; // Test for a deadlock bug in Mutex::Fer(). // The sequence of events that lead to the deadlock is: // 1. waiter1 blocks on cv in read mode (mu bits = 0). // 2. waiter2 blocks on mu in either mode (mu bits = kMuWait). // 3. main thread locks mu, sets cond1, unlocks mu (mu bits = kMuWait). // 4. main thread signals on cv and this eventually calls Mutex::Fer(). // Currently Fer wakes waiter1 since mu bits = kMuWait (mutex is unlocked). // Before the bug fix Fer neither woke waiter1 nor queued it on mutex, // which resulted in deadlock. TEST_P(CondVarWaitDeadlock, Test) { auto waiter1 = CreatePool(1); auto waiter2 = CreatePool(1); waiter1->Schedule([this] { this->Waiter1(); }); waiter2->Schedule([this] { this->Waiter2(); }); // Wait while threads block (best-effort is fine). absl::SleepFor(absl::Milliseconds(100)); // Wake condwaiter. mu.Lock(); cond1 = true; if (signal_unlocked) { mu.Unlock(); cv.Signal(); } else { cv.Signal(); mu.Unlock(); } waiter1.reset(); // "join" waiter1 // Wake waiter. mu.Lock(); cond2 = true; mu.Unlock(); waiter2.reset(); // "join" waiter2 } INSTANTIATE_TEST_SUITE_P(CondVarWaitDeadlockTest, CondVarWaitDeadlock, ::testing::Range(0, 8), ::testing::PrintToStringParamName()); // -------------------------------------------------------- // Test for fix of bug in DequeueAllWakeable() // Bug was that if there was more than one waiting reader // and all should be woken, the most recently blocked one // would not be. struct DequeueAllWakeableBugStruct { absl::Mutex mu; absl::Mutex mu2; // protects all fields below int unfinished_count; // count of unfinished readers; under mu2 bool done1; // unfinished_count == 0; under mu2 int finished_count; // count of finished readers, under mu2 bool done2; // finished_count == 0; under mu2 }; // Test for regression of a bug in loop of DequeueAllWakeable() static void AcquireAsReader(DequeueAllWakeableBugStruct *x) { x->mu.ReaderLock(); x->mu2.Lock(); x->unfinished_count--; x->done1 = (x->unfinished_count == 0); x->mu2.Unlock(); // make sure that both readers acquired mu before we release it. absl::SleepFor(absl::Seconds(2)); x->mu.ReaderUnlock(); x->mu2.Lock(); x->finished_count--; x->done2 = (x->finished_count == 0); x->mu2.Unlock(); } // Test for regression of a bug in loop of DequeueAllWakeable() TEST(Mutex, MutexReaderWakeupBug) { auto tp = CreateDefaultPool(); DequeueAllWakeableBugStruct x; x.unfinished_count = 2; x.done1 = false; x.finished_count = 2; x.done2 = false; x.mu.Lock(); // acquire mu exclusively // queue two thread that will block on reader locks on x.mu tp->Schedule(std::bind(&AcquireAsReader, &x)); tp->Schedule(std::bind(&AcquireAsReader, &x)); absl::SleepFor(absl::Seconds(1)); // give time for reader threads to block x.mu.Unlock(); // wake them up // both readers should finish promptly EXPECT_TRUE( x.mu2.LockWhenWithTimeout(absl::Condition(&x.done1), absl::Seconds(10))); x.mu2.Unlock(); EXPECT_TRUE( x.mu2.LockWhenWithTimeout(absl::Condition(&x.done2), absl::Seconds(10))); x.mu2.Unlock(); } struct LockWhenTestStruct { absl::Mutex mu1; bool cond = false; absl::Mutex mu2; bool waiting = false; }; static bool LockWhenTestIsCond(LockWhenTestStruct* s) { s->mu2.Lock(); s->waiting = true; s->mu2.Unlock(); return s->cond; } static void LockWhenTestWaitForIsCond(LockWhenTestStruct* s) { s->mu1.LockWhen(absl::Condition(&LockWhenTestIsCond, s)); s->mu1.Unlock(); } TEST(Mutex, LockWhen) { LockWhenTestStruct s; std::thread t(LockWhenTestWaitForIsCond, &s); s.mu2.LockWhen(absl::Condition(&s.waiting)); s.mu2.Unlock(); s.mu1.Lock(); s.cond = true; s.mu1.Unlock(); t.join(); } TEST(Mutex, LockWhenGuard) { absl::Mutex mu; int n = 30; bool done = false; // We don't inline the lambda because the conversion is ambiguous in MSVC. bool (*cond_eq_10)(int *) = [](int *p) { return *p == 10; }; bool (*cond_lt_10)(int *) = [](int *p) { return *p < 10; }; std::thread t1([&mu, &n, &done, cond_eq_10]() { absl::ReaderMutexLock lock(&mu, absl::Condition(cond_eq_10, &n)); done = true; }); std::thread t2[10]; for (std::thread &t : t2) { t = std::thread([&mu, &n, cond_lt_10]() { absl::WriterMutexLock lock(&mu, absl::Condition(cond_lt_10, &n)); ++n; }); } { absl::MutexLock lock(&mu); n = 0; } for (std::thread &t : t2) t.join(); t1.join(); EXPECT_TRUE(done); EXPECT_EQ(n, 10); } // -------------------------------------------------------- // The following test requires Mutex::ReaderLock to be a real shared // lock, which is not the case in all builds. #if !defined(ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE) // Test for fix of bug in UnlockSlow() that incorrectly decremented the reader // count when putting a thread to sleep waiting for a false condition when the // lock was not held. // For this bug to strike, we make a thread wait on a free mutex with no // waiters by causing its wakeup condition to be false. Then the // next two acquirers must be readers. The bug causes the lock // to be released when one reader unlocks, rather than both. struct ReaderDecrementBugStruct { bool cond; // to delay first thread (under mu) int done; // reference count (under mu) absl::Mutex mu; bool waiting_on_cond; // under mu2 bool have_reader_lock; // under mu2 bool complete; // under mu2 absl::Mutex mu2; // > mu }; // L >= mu, L < mu_waiting_on_cond static bool IsCond(void *v) { ReaderDecrementBugStruct *x = reinterpret_cast<ReaderDecrementBugStruct *>(v); x->mu2.Lock(); x->waiting_on_cond = true; x->mu2.Unlock(); return x->cond; } // L >= mu static bool AllDone(void *v) { ReaderDecrementBugStruct *x = reinterpret_cast<ReaderDecrementBugStruct *>(v); return x->done == 0; } // L={} static void WaitForCond(ReaderDecrementBugStruct *x) { absl::Mutex dummy; absl::MutexLock l(&dummy); x->mu.LockWhen(absl::Condition(&IsCond, x)); x->done--; x->mu.Unlock(); } // L={} static void GetReadLock(ReaderDecrementBugStruct *x) { x->mu.ReaderLock(); x->mu2.Lock(); x->have_reader_lock = true; x->mu2.Await(absl::Condition(&x->complete)); x->mu2.Unlock(); x->mu.ReaderUnlock(); x->mu.Lock(); x->done--; x->mu.Unlock(); } // Test for reader counter being decremented incorrectly by waiter // with false condition. TEST(Mutex, MutexReaderDecrementBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { ReaderDecrementBugStruct x; x.cond = false; x.waiting_on_cond = false; x.have_reader_lock = false; x.complete = false; x.done = 2; // initial ref count // Run WaitForCond() and wait for it to sleep std::thread thread1(WaitForCond, &x); x.mu2.LockWhen(absl::Condition(&x.waiting_on_cond)); x.mu2.Unlock(); // Run GetReadLock(), and wait for it to get the read lock std::thread thread2(GetReadLock, &x); x.mu2.LockWhen(absl::Condition(&x.have_reader_lock)); x.mu2.Unlock(); // Get the reader lock ourselves, and release it. x.mu.ReaderLock(); x.mu.ReaderUnlock(); // The lock should be held in read mode by GetReadLock(). // If we have the bug, the lock will be free. x.mu.AssertReaderHeld(); // Wake up all the threads. x.mu2.Lock(); x.complete = true; x.mu2.Unlock(); // TODO(delesley): turn on analysis once lock upgrading is supported. // (This call upgrades the lock from shared to exclusive.) x.mu.Lock(); x.cond = true; x.mu.Await(absl::Condition(&AllDone, &x)); x.mu.Unlock(); thread1.join(); thread2.join(); } #endif // !ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE // Test that we correctly handle the situation when a lock is // held and then destroyed (w/o unlocking). #ifdef ABSL_HAVE_THREAD_SANITIZER // TSAN reports errors when locked Mutexes are destroyed. TEST(Mutex, DISABLED_LockedMutexDestructionBug) NO_THREAD_SAFETY_ANALYSIS { #else TEST(Mutex, LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { #endif for (int i = 0; i != 10; i++) { // Create, lock and destroy 10 locks. const int kNumLocks = 10; auto mu = absl::make_unique<absl::Mutex[]>(kNumLocks); for (int j = 0; j != kNumLocks; j++) { if ((j % 2) == 0) { mu[j].WriterLock(); } else { mu[j].ReaderLock(); } } } } // -------------------------------------------------------- // Test for bug with pattern of readers using a condvar. The bug was that if a // reader went to sleep on a condition variable while one or more other readers // held the lock, but there were no waiters, the reader count (held in the // mutex word) would be lost. (This is because Enqueue() had at one time // always placed the thread on the Mutex queue. Later (CL 4075610), to // tolerate re-entry into Mutex from a Condition predicate, Enqueue() was // changed so that it could also place a thread on a condition-variable. This // introduced the case where Enqueue() returned with an empty queue, and this // case was handled incorrectly in one place.) static void ReaderForReaderOnCondVar(absl::Mutex *mu, absl::CondVar *cv, int *running) { std::random_device dev; std::mt19937 gen(dev()); std::uniform_int_distribution<int> random_millis(0, 15); mu->ReaderLock(); while (*running == 3) { absl::SleepFor(absl::Milliseconds(random_millis(gen))); cv->WaitWithTimeout(mu, absl::Milliseconds(random_millis(gen))); } mu->ReaderUnlock(); mu->Lock(); (*running)--; mu->Unlock(); } struct True { template <class... Args> bool operator()(Args...) const { return true; } }; struct DerivedTrue : True {}; TEST(Mutex, FunctorCondition) { { // Variadic True f; EXPECT_TRUE(absl::Condition(&f).Eval()); } { // Inherited DerivedTrue g; EXPECT_TRUE(absl::Condition(&g).Eval()); } { // lambda int value = 3; auto is_zero = [&value] { return value == 0; }; absl::Condition c(&is_zero); EXPECT_FALSE(c.Eval()); value = 0; EXPECT_TRUE(c.Eval()); } { // bind int value = 0; auto is_positive = std::bind(std::less<int>(), 0, std::cref(value)); absl::Condition c(&is_positive); EXPECT_FALSE(c.Eval()); value = 1; EXPECT_TRUE(c.Eval()); } { // std::function int value = 3; std::function<bool()> is_zero = [&value] { return value == 0; }; absl::Condition c(&is_zero); EXPECT_FALSE(c.Eval()); value = 0; EXPECT_TRUE(c.Eval()); } } static bool IntIsZero(int *x) { return *x == 0; } // Test for reader waiting condition variable when there are other readers // but no waiters. TEST(Mutex, TestReaderOnCondVar) { auto tp = CreateDefaultPool(); absl::Mutex mu; absl::CondVar cv; int running = 3; tp->Schedule(std::bind(&ReaderForReaderOnCondVar, &mu, &cv, &running)); tp->Schedule(std::bind(&ReaderForReaderOnCondVar, &mu, &cv, &running)); absl::SleepFor(absl::Seconds(2)); mu.Lock(); running--; mu.Await(absl::Condition(&IntIsZero, &running)); mu.Unlock(); } // -------------------------------------------------------- struct AcquireFromConditionStruct { absl::Mutex mu0; // protects value, done int value; // times condition function is called; under mu0, bool done; // done with test? under mu0 absl::Mutex mu1; // used to attempt to mess up state of mu0 absl::CondVar cv; // so the condition function can be invoked from // CondVar::Wait(). }; static bool ConditionWithAcquire(AcquireFromConditionStruct *x) { x->value++; // count times this function is called if (x->value == 2 || x->value == 3) { // On the second and third invocation of this function, sleep for 100ms, // but with the side-effect of altering the state of a Mutex other than // than one for which this is a condition. The spec now explicitly allows // this side effect; previously it did not. it was illegal. bool always_false = false; x->mu1.LockWhenWithTimeout(absl::Condition(&always_false), absl::Milliseconds(100)); x->mu1.Unlock(); } ABSL_RAW_CHECK(x->value < 4, "should not be invoked a fourth time"); // We arrange for the condition to return true on only the 2nd and 3rd calls. return x->value == 2 || x->value == 3; } static void WaitForCond2(AcquireFromConditionStruct *x) { // wait for cond0 to become true x->mu0.LockWhen(absl::Condition(&ConditionWithAcquire, x)); x->done = true; x->mu0.Unlock(); } // Test for Condition whose function acquires other Mutexes TEST(Mutex, AcquireFromCondition) { auto tp = CreateDefaultPool(); AcquireFromConditionStruct x; x.value = 0; x.done = false; tp->Schedule( std::bind(&WaitForCond2, &x)); // run WaitForCond2() in a thread T // T will hang because the first invocation of ConditionWithAcquire() will // return false. absl::SleepFor(absl::Milliseconds(500)); // allow T time to hang x.mu0.Lock(); x.cv.WaitWithTimeout(&x.mu0, absl::Milliseconds(500)); // wake T // T will be woken because the Wait() will call ConditionWithAcquire() // for the second time, and it will return true. x.mu0.Unlock(); // T will then acquire the lock and recheck its own condition. // It will find the condition true, as this is the third invocation, // but the use of another Mutex by the calling function will // cause the old mutex implementation to think that the outer // LockWhen() has timed out because the inner LockWhenWithTimeout() did. // T will then check the condition a fourth time because it finds a // timeout occurred. This should not happen in the new // implementation that allows the Condition function to use Mutexes. // It should also succeed, even though the Condition function // is being invoked from CondVar::Wait, and thus this thread // is conceptually waiting both on the condition variable, and on mu2. x.mu0.LockWhen(absl::Condition(&x.done)); x.mu0.Unlock(); } TEST(Mutex, DeadlockDetector) { absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); // check that we can call ForgetDeadlockInfo() on a lock with the lock held absl::Mutex m1; absl::Mutex m2; absl::Mutex m3; absl::Mutex m4; m1.Lock(); // m1 gets ID1 m2.Lock(); // m2 gets ID2 m3.Lock(); // m3 gets ID3 m3.Unlock(); m2.Unlock(); // m1 still held m1.ForgetDeadlockInfo(); // m1 loses ID m2.Lock(); // m2 gets ID2 m3.Lock(); // m3 gets ID3 m4.Lock(); // m4 gets ID4 m3.Unlock(); m2.Unlock(); m4.Unlock(); m1.Unlock(); } // Bazel has a test "warning" file that programs can write to if the // test should pass with a warning. This class disables the warning // file until it goes out of scope. class ScopedDisableBazelTestWarnings { public: ScopedDisableBazelTestWarnings() { #ifdef _WIN32 char file[MAX_PATH]; if (GetEnvironmentVariableA(kVarName, file, sizeof(file)) < sizeof(file)) { warnings_output_file_ = file; SetEnvironmentVariableA(kVarName, nullptr); } #else const char *file = getenv(kVarName); if (file != nullptr) { warnings_output_file_ = file; unsetenv(kVarName); } #endif } ~ScopedDisableBazelTestWarnings() { if (!warnings_output_file_.empty()) { #ifdef _WIN32 SetEnvironmentVariableA(kVarName, warnings_output_file_.c_str()); #else setenv(kVarName, warnings_output_file_.c_str(), 0); #endif } } private: static const char kVarName[]; std::string warnings_output_file_; }; const char ScopedDisableBazelTestWarnings::kVarName[] = "TEST_WARNINGS_OUTPUT_FILE"; #ifdef ABSL_HAVE_THREAD_SANITIZER // This test intentionally creates deadlocks to test the deadlock detector. TEST(Mutex, DISABLED_DeadlockDetectorBazelWarning) { #else TEST(Mutex, DeadlockDetectorBazelWarning) { #endif absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kReport); // Cause deadlock detection to detect something, if it's // compiled in and enabled. But turn off the bazel warning. ScopedDisableBazelTestWarnings disable_bazel_test_warnings; absl::Mutex mu0; absl::Mutex mu1; bool got_mu0 = mu0.TryLock(); mu1.Lock(); // acquire mu1 while holding mu0 if (got_mu0) { mu0.Unlock(); } if (mu0.TryLock()) { // try lock shouldn't cause deadlock detector to fire mu0.Unlock(); } mu0.Lock(); // acquire mu0 while holding mu1; should get one deadlock // report here mu0.Unlock(); mu1.Unlock(); absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); } // This test is tagged with NO_THREAD_SAFETY_ANALYSIS because the // annotation-based static thread-safety analysis is not currently // predicate-aware and cannot tell if the two for-loops that acquire and // release the locks have the same predicates. TEST(Mutex, DeadlockDetectorStressTest) ABSL_NO_THREAD_SAFETY_ANALYSIS { // Stress test: Here we create a large number of locks and use all of them. // If a deadlock detector keeps a full graph of lock acquisition order, // it will likely be too slow for this test to pass. const int n_locks = 1 << 17; auto array_of_locks = absl::make_unique<absl::Mutex[]>(n_locks); for (int i = 0; i < n_locks; i++) { int end = std::min(n_locks, i + 5); // acquire and then release locks i, i+1, ..., i+4 for (int j = i; j < end; j++) { array_of_locks[j].Lock(); } for (int j = i; j < end; j++) { array_of_locks[j].Unlock(); } } } #ifdef ABSL_HAVE_THREAD_SANITIZER // TSAN reports errors when locked Mutexes are destroyed. TEST(Mutex, DISABLED_DeadlockIdBug) NO_THREAD_SAFETY_ANALYSIS { #else TEST(Mutex, DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { #endif // Test a scenario where a cached deadlock graph node id in the // list of held locks is not invalidated when the corresponding // mutex is deleted. absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); // Mutex that will be destroyed while being held absl::Mutex *a = new absl::Mutex; // Other mutexes needed by test absl::Mutex b, c; // Hold mutex. a->Lock(); // Force deadlock id assignment by acquiring another lock. b.Lock(); b.Unlock(); // Delete the mutex. The Mutex destructor tries to remove held locks, // but the attempt isn't foolproof. It can fail if: // (a) Deadlock detection is currently disabled. // (b) The destruction is from another thread. // We exploit (a) by temporarily disabling deadlock detection. absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kIgnore); delete a; absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); // Now acquire another lock which will force a deadlock id assignment. // We should end up getting assigned the same deadlock id that was // freed up when "a" was deleted, which will cause a spurious deadlock // report if the held lock entry for "a" was not invalidated. c.Lock(); c.Unlock(); } // -------------------------------------------------------- // Test for timeouts/deadlines on condition waits that are specified using // absl::Duration and absl::Time. For each waiting function we test with // a timeout/deadline that has already expired/passed, one that is infinite // and so never expires/passes, and one that will expire/pass in the near // future. static absl::Duration TimeoutTestAllowedSchedulingDelay() { // Note: we use a function here because Microsoft Visual Studio fails to // properly initialize constexpr static absl::Duration variables. return absl::Milliseconds(150); } // Returns true if `actual_delay` is close enough to `expected_delay` to pass // the timeouts/deadlines test. Otherwise, logs warnings and returns false. ABSL_MUST_USE_RESULT static bool DelayIsWithinBounds(absl::Duration expected_delay, absl::Duration actual_delay) { bool pass = true; // Do not allow the observed delay to be less than expected. This may occur // in practice due to clock skew or when the synchronization primitives use a // different clock than absl::Now(), but these cases should be handled by the // the retry mechanism in each TimeoutTest. if (actual_delay < expected_delay) { ABSL_RAW_LOG(WARNING, "Actual delay %s was too short, expected %s (difference %s)", absl::FormatDuration(actual_delay).c_str(), absl::FormatDuration(expected_delay).c_str(), absl::FormatDuration(actual_delay - expected_delay).c_str()); pass = false; } // If the expected delay is <= zero then allow a small error tolerance, since // we do not expect context switches to occur during test execution. // Otherwise, thread scheduling delays may be substantial in rare cases, so // tolerate up to kTimeoutTestAllowedSchedulingDelay of error. absl::Duration tolerance = expected_delay <= absl::ZeroDuration() ? absl::Milliseconds(10) : TimeoutTestAllowedSchedulingDelay(); if (actual_delay > expected_delay + tolerance) { ABSL_RAW_LOG(WARNING, "Actual delay %s was too long, expected %s (difference %s)", absl::FormatDuration(actual_delay).c_str(), absl::FormatDuration(expected_delay).c_str(), absl::FormatDuration(actual_delay - expected_delay).c_str()); pass = false; } return pass; } // Parameters for TimeoutTest, below. struct TimeoutTestParam { // The file and line number (used for logging purposes only). const char *from_file; int from_line; // Should the absolute deadline API based on absl::Time be tested? If false, // the relative deadline API based on absl::Duration is tested. bool use_absolute_deadline; // The deadline/timeout used when calling the API being tested // (e.g. Mutex::LockWhenWithDeadline). absl::Duration wait_timeout; // The delay before the condition will be set true by the test code. If zero // or negative, the condition is set true immediately (before calling the API // being tested). Otherwise, if infinite, the condition is never set true. // Otherwise a closure is scheduled for the future that sets the condition // true. absl::Duration satisfy_condition_delay; // The expected result of the condition after the call to the API being // tested. Generally `true` means the condition was true when the API returns, // `false` indicates an expected timeout. bool expected_result; // The expected delay before the API under test returns. This is inherently // flaky, so some slop is allowed (see `DelayIsWithinBounds` above), and the // test keeps trying indefinitely until this constraint passes. absl::Duration expected_delay; }; // Print a `TimeoutTestParam` to a debug log. std::ostream &operator<<(std::ostream &os, const TimeoutTestParam ¶m) { return os << "from: " << param.from_file << ":" << param.from_line << " use_absolute_deadline: " << (param.use_absolute_deadline ? "true" : "false") << " wait_timeout: " << param.wait_timeout << " satisfy_condition_delay: " << param.satisfy_condition_delay << " expected_result: " << (param.expected_result ? "true" : "false") << " expected_delay: " << param.expected_delay; } std::string FormatString(const TimeoutTestParam ¶m) { std::ostringstream os; os << param; return os.str(); } // Like `thread::Executor::ScheduleAt` except: // a) Delays zero or negative are executed immediately in the current thread. // b) Infinite delays are never scheduled. // c) Calls this test's `ScheduleAt` helper instead of using `pool` directly. static void RunAfterDelay(absl::Duration delay, absl::synchronization_internal::ThreadPool *pool, const std::function<void()> &callback) { if (delay <= absl::ZeroDuration()) { callback(); // immediate } else if (delay != absl::InfiniteDuration()) { ScheduleAfter(pool, delay, callback); } } class TimeoutTest : public ::testing::Test, public ::testing::WithParamInterface<TimeoutTestParam> {}; std::vector<TimeoutTestParam> MakeTimeoutTestParamValues() { // The `finite` delay is a finite, relatively short, delay. We make it larger // than our allowed scheduling delay (slop factor) to avoid confusion when // diagnosing test failures. The other constants here have clear meanings. const absl::Duration finite = 3 * TimeoutTestAllowedSchedulingDelay(); const absl::Duration never = absl::InfiniteDuration(); const absl::Duration negative = -absl::InfiniteDuration(); const absl::Duration immediate = absl::ZeroDuration(); // Every test case is run twice; once using the absolute deadline API and once // using the relative timeout API. std::vector<TimeoutTestParam> values; for (bool use_absolute_deadline : {false, true}) { // Tests with a negative timeout (deadline in the past), which should // immediately return current state of the condition. // The condition is already true: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, negative, // wait_timeout immediate, // satisfy_condition_delay true, // expected_result immediate, // expected_delay }); // The condition becomes true, but the timeout has already expired: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, negative, // wait_timeout finite, // satisfy_condition_delay false, // expected_result immediate // expected_delay }); // The condition never becomes true: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, negative, // wait_timeout never, // satisfy_condition_delay false, // expected_result immediate // expected_delay }); // Tests with an infinite timeout (deadline in the infinite future), which // should only return when the condition becomes true. // The condition is already true: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, never, // wait_timeout immediate, // satisfy_condition_delay true, // expected_result immediate // expected_delay }); // The condition becomes true before the (infinite) expiry: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, never, // wait_timeout finite, // satisfy_condition_delay true, // expected_result finite, // expected_delay }); // Tests with a (small) finite timeout (deadline soon), with the condition // becoming true both before and after its expiry. // The condition is already true: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, never, // wait_timeout immediate, // satisfy_condition_delay true, // expected_result immediate // expected_delay }); // The condition becomes true before the expiry: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, finite * 2, // wait_timeout finite, // satisfy_condition_delay true, // expected_result finite // expected_delay }); // The condition becomes true, but the timeout has already expired: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, finite, // wait_timeout finite * 2, // satisfy_condition_delay false, // expected_result finite // expected_delay }); // The condition never becomes true: values.push_back(TimeoutTestParam{ __FILE__, __LINE__, use_absolute_deadline, finite, // wait_timeout never, // satisfy_condition_delay false, // expected_result finite // expected_delay }); } return values; } // Instantiate `TimeoutTest` with `MakeTimeoutTestParamValues()`. INSTANTIATE_TEST_SUITE_P(All, TimeoutTest, testing::ValuesIn(MakeTimeoutTestParamValues())); TEST_P(TimeoutTest, Await) { const TimeoutTestParam params = GetParam(); ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 1;; ++attempt) { ABSL_RAW_LOG(INFO, "Attempt %d", attempt); absl::Mutex mu; bool value = false; // condition value (under mu) std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { absl::MutexLock l(&mu); value = true; }); absl::MutexLock lock(&mu); absl::Time start_time = absl::Now(); absl::Condition cond(&value); bool result = params.use_absolute_deadline ? mu.AwaitWithDeadline(cond, start_time + params.wait_timeout) : mu.AwaitWithTimeout(cond, params.wait_timeout); if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { EXPECT_EQ(params.expected_result, result); break; } } } TEST_P(TimeoutTest, LockWhen) { const TimeoutTestParam params = GetParam(); ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 1;; ++attempt) { ABSL_RAW_LOG(INFO, "Attempt %d", attempt); absl::Mutex mu; bool value = false; // condition value (under mu) std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { absl::MutexLock l(&mu); value = true; }); absl::Time start_time = absl::Now(); absl::Condition cond(&value); bool result = params.use_absolute_deadline ? mu.LockWhenWithDeadline(cond, start_time + params.wait_timeout) : mu.LockWhenWithTimeout(cond, params.wait_timeout); mu.Unlock(); if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { EXPECT_EQ(params.expected_result, result); break; } } } TEST_P(TimeoutTest, ReaderLockWhen) { const TimeoutTestParam params = GetParam(); ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 0;; ++attempt) { ABSL_RAW_LOG(INFO, "Attempt %d", attempt); absl::Mutex mu; bool value = false; // condition value (under mu) std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { absl::MutexLock l(&mu); value = true; }); absl::Time start_time = absl::Now(); bool result = params.use_absolute_deadline ? mu.ReaderLockWhenWithDeadline(absl::Condition(&value), start_time + params.wait_timeout) : mu.ReaderLockWhenWithTimeout(absl::Condition(&value), params.wait_timeout); mu.ReaderUnlock(); if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { EXPECT_EQ(params.expected_result, result); break; } } } TEST_P(TimeoutTest, Wait) { const TimeoutTestParam params = GetParam(); ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 0;; ++attempt) { ABSL_RAW_LOG(INFO, "Attempt %d", attempt); absl::Mutex mu; bool value = false; // condition value (under mu) absl::CondVar cv; // signals a change of `value` std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { absl::MutexLock l(&mu); value = true; cv.Signal(); }); absl::MutexLock lock(&mu); absl::Time start_time = absl::Now(); absl::Duration timeout = params.wait_timeout; absl::Time deadline = start_time + timeout; while (!value) { if (params.use_absolute_deadline ? cv.WaitWithDeadline(&mu, deadline) : cv.WaitWithTimeout(&mu, timeout)) { break; // deadline/timeout exceeded } timeout = deadline - absl::Now(); // recompute } bool result = value; // note: `mu` is still held if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { EXPECT_EQ(params.expected_result, result); break; } } } TEST(Mutex, Logging) { // Allow user to look at logging output absl::Mutex logged_mutex; logged_mutex.EnableDebugLog("fido_mutex"); absl::CondVar logged_cv; logged_cv.EnableDebugLog("rover_cv"); logged_mutex.Lock(); logged_cv.WaitWithTimeout(&logged_mutex, absl::Milliseconds(20)); logged_mutex.Unlock(); logged_mutex.ReaderLock(); logged_mutex.ReaderUnlock(); logged_mutex.Lock(); logged_mutex.Unlock(); logged_cv.Signal(); logged_cv.SignalAll(); } // -------------------------------------------------------- // Generate the vector of thread counts for tests parameterized on thread count. static std::vector<int> AllThreadCountValues() { if (kExtendedTest) { return {2, 4, 8, 10, 16, 20, 24, 30, 32}; } return {2, 4, 10}; } // A test fixture parameterized by thread count. class MutexVariableThreadCountTest : public ::testing::TestWithParam<int> {}; // Instantiate the above with AllThreadCountOptions(). INSTANTIATE_TEST_SUITE_P(ThreadCounts, MutexVariableThreadCountTest, ::testing::ValuesIn(AllThreadCountValues()), ::testing::PrintToStringParamName()); // Reduces iterations by some factor for slow platforms // (determined empirically). static int ScaleIterations(int x) { // ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE is set in the implementation // of Mutex that uses either std::mutex or pthread_mutex_t. Use // these as keys to determine the slow implementation. #if defined(ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE) return x / 10; #else return x; #endif } TEST_P(MutexVariableThreadCountTest, Mutex) { int threads = GetParam(); int iterations = ScaleIterations(10000000) / threads; int operations = threads * iterations; EXPECT_EQ(RunTest(&TestMu, threads, iterations, operations), operations); #if !defined(ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED) iterations = std::min(iterations, 10); operations = threads * iterations; EXPECT_EQ(RunTestWithInvariantDebugging(&TestMu, threads, iterations, operations, CheckSumG0G1), operations); #endif } TEST_P(MutexVariableThreadCountTest, Try) { int threads = GetParam(); int iterations = 1000000 / threads; int operations = iterations * threads; EXPECT_EQ(RunTest(&TestTry, threads, iterations, operations), operations); #if !defined(ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED) iterations = std::min(iterations, 10); operations = threads * iterations; EXPECT_EQ(RunTestWithInvariantDebugging(&TestTry, threads, iterations, operations, CheckSumG0G1), operations); #endif } TEST_P(MutexVariableThreadCountTest, R20ms) { int threads = GetParam(); int iterations = 100; int operations = iterations * threads; EXPECT_EQ(RunTest(&TestR20ms, threads, iterations, operations), 0); } TEST_P(MutexVariableThreadCountTest, RW) { int threads = GetParam(); int iterations = ScaleIterations(20000000) / threads; int operations = iterations * threads; EXPECT_EQ(RunTest(&TestRW, threads, iterations, operations), operations / 2); #if !defined(ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED) iterations = std::min(iterations, 10); operations = threads * iterations; EXPECT_EQ(RunTestWithInvariantDebugging(&TestRW, threads, iterations, operations, CheckSumG0G1), operations / 2); #endif } TEST_P(MutexVariableThreadCountTest, Await) { int threads = GetParam(); int iterations = ScaleIterations(500000); int operations = iterations; EXPECT_EQ(RunTest(&TestAwait, threads, iterations, operations), operations); } TEST_P(MutexVariableThreadCountTest, SignalAll) { int threads = GetParam(); int iterations = 200000 / threads; int operations = iterations; EXPECT_EQ(RunTest(&TestSignalAll, threads, iterations, operations), operations); } TEST(Mutex, Signal) { int threads = 2; // TestSignal must use two threads int iterations = 200000; int operations = iterations; EXPECT_EQ(RunTest(&TestSignal, threads, iterations, operations), operations); } TEST(Mutex, Timed) { int threads = 10; // Use a fixed thread count of 10 int iterations = 1000; int operations = iterations; EXPECT_EQ(RunTest(&TestCVTimeout, threads, iterations, operations), operations); } TEST(Mutex, CVTime) { int threads = 10; // Use a fixed thread count of 10 int iterations = 1; EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), threads * iterations); } TEST(Mutex, MuTime) { int threads = 10; // Use a fixed thread count of 10 int iterations = 1; EXPECT_EQ(RunTest(&TestMuTime, threads, iterations, 1), threads * iterations); } } // namespace