diff options
author | Vincent Ambo <mail@tazj.in> | 2022-02-07T23·05+0300 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2022-02-07T23·09+0000 |
commit | 5aa5d282eac56a21e74611c1cdbaa97bb5db2dca (patch) | |
tree | 8cc5dce8157a1470ff76719dd15d65f648a05522 /third_party/abseil_cpp/absl/synchronization/mutex_test.cc | |
parent | a25675804c4f429fab5ee5201fe25e89865dfd13 (diff) |
chore(3p/abseil_cpp): unvendor abseil_cpp r/3786
we weren't actually using these sources anymore, okay? Change-Id: If701571d9716de308d3512e1eb22c35db0877a66 Reviewed-on: https://cl.tvl.fyi/c/depot/+/5248 Tested-by: BuildkiteCI Reviewed-by: grfn <grfn@gws.fyi> Autosubmit: tazjin <tazjin@tvl.su>
Diffstat (limited to 'third_party/abseil_cpp/absl/synchronization/mutex_test.cc')
-rw-r--r-- | third_party/abseil_cpp/absl/synchronization/mutex_test.cc | 1706 |
1 files changed, 0 insertions, 1706 deletions
diff --git a/third_party/abseil_cpp/absl/synchronization/mutex_test.cc b/third_party/abseil_cpp/absl/synchronization/mutex_test.cc deleted file mode 100644 index 058f757b482f..000000000000 --- a/third_party/abseil_cpp/absl/synchronization/mutex_test.cc +++ /dev/null @@ -1,1706 +0,0 @@ -// 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 |