diff options
Diffstat (limited to 'third_party/abseil_cpp/absl/synchronization/mutex_test.cc')
-rw-r--r-- | third_party/abseil_cpp/absl/synchronization/mutex_test.cc | 1675 |
1 files changed, 1675 insertions, 0 deletions
diff --git a/third_party/abseil_cpp/absl/synchronization/mutex_test.cc b/third_party/abseil_cpp/absl/synchronization/mutex_test.cc new file mode 100644 index 000000000000..afb363af6127 --- /dev/null +++ b/third_party/abseil_cpp/absl/synchronization/mutex_test.cc @@ -0,0 +1,1675 @@ +// 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/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(); +} + +// -------------------------------------------------------- +// 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 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(); +} + +// The deadlock detector is not part of non-prod builds, so do not test it. +#if !defined(ABSL_INTERNAL_USE_NONPROD_MUTEX) + +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 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, DeadlockDetectorStessTest) 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 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(); +} +#endif // !defined(ABSL_INTERNAL_USE_NONPROD_MUTEX) + +// -------------------------------------------------------- +// 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 |