| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #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> |
| #include <type_traits> |
| #include <vector> |
|
|
| #include "gtest/gtest.h" |
| #include "absl/base/attributes.h" |
| #include "absl/base/config.h" |
| #include "absl/base/internal/sysinfo.h" |
| #include "absl/log/check.h" |
| #include "absl/log/log.h" |
| #include "absl/memory/memory.h" |
| #include "absl/synchronization/internal/create_thread_identity.h" |
| #include "absl/synchronization/internal/thread_pool.h" |
| #include "absl/time/clock.h" |
| #include "absl/time/time.h" |
|
|
| #ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM |
| #include <pthread.h> |
| #include <string.h> |
| #endif |
|
|
| namespace { |
|
|
| |
| 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); |
| } |
|
|
| |
| |
| 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 ScopedInvariantDebugging { |
| ScopedInvariantDebugging() { absl::EnableMutexInvariantDebugging(true); } |
| ~ScopedInvariantDebugging() { absl::EnableMutexInvariantDebugging(false); } |
| }; |
|
|
| struct TestContext { |
| int iterations; |
| int threads; |
| int g0; |
| int g1; |
| absl::Mutex mu; |
| absl::CondVar cv; |
| }; |
|
|
| |
| 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); |
| CHECK_EQ(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); |
| CHECK_EQ(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)); |
| 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) { |
| CHECK_EQ(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) { |
| CHECK_EQ(cxt->iterations, 1) << "TestTime should only use 1 iteration"; |
| CHECK_GT(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 { |
| CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) |
| << "TestTime failed"; |
| } |
| absl::Duration elapsed = absl::Now() - start; |
| CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) |
| << "TestTime failed"; |
| CHECK_EQ(cxt->g0, 1) << "TestTime failed"; |
|
|
| start = absl::Now(); |
| if (use_cv) { |
| cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); |
| } else { |
| CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) |
| << "TestTime failed"; |
| } |
| elapsed = absl::Now() - start; |
| 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 { |
| CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(4))) |
| << "TestTime failed"; |
| } |
| elapsed = absl::Now() - start; |
| CHECK(absl::Seconds(3.9) <= elapsed && elapsed <= absl::Seconds(6.0)) |
| << "TestTime failed"; |
| CHECK_GE(cxt->g0, 3) << "TestTime failed"; |
|
|
| start = absl::Now(); |
| if (use_cv) { |
| cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); |
| } else { |
| CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) |
| << "TestTime failed"; |
| } |
| elapsed = absl::Now() - start; |
| 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 { |
| CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) |
| << "TestTime failed"; |
| } |
| elapsed = absl::Now() - start; |
| CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) |
| << "TestTime failed"; |
| CHECK_EQ(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 { |
| CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Milliseconds(500))) |
| << "TestTime failed"; |
| } |
| const absl::Duration elapsed = absl::Now() - start; |
| 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 { |
| 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(); |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| static int RunTest(void (*test)(TestContext *cxt, int), int threads, |
| int iterations, int operations) { |
| TestContext cxt; |
| return RunTestCommon(&cxt, test, threads, iterations, operations); |
| } |
|
|
| |
| |
| |
| |
| #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 *)) { |
| ScopedInvariantDebugging scoped_debugging; |
| SetInvariantChecked(false); |
| TestContext cxt; |
| cxt.mu.EnableInvariantDebugging(invariant, &cxt); |
| int ret = RunTestCommon(&cxt, test, threads, iterations, operations); |
| CHECK(GetInvariantChecked()) << "Invariant not checked"; |
| return ret; |
| } |
| #endif |
|
|
| |
| |
| 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(Mutex, CondVarWaitSignalsAwait) { |
| |
| 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(); |
|
|
| |
| |
| 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(); |
| |
|
|
| |
| |
| state.release = true; |
| state.released_cv.Wait(&state.release_mu); |
| state.release_mu.Unlock(); |
| } |
|
|
| |
| |
| TEST(Mutex, CondVarWaitWithTimeoutSignalsAwait) { |
| |
| 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(); |
|
|
| |
| |
| 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(); |
| |
|
|
| |
| |
| 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(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)); |
| |
| |
|
|
| |
| |
| bool always_false = false; |
| x.mu.LockWhenWithTimeout(absl::Condition(&always_false), |
| absl::Milliseconds(500)); |
|
|
| |
|
|
| x.a = true; |
| x.mu.Await(absl::Condition(&NoAWaiters, &x)); |
| 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_P(CondVarWaitDeadlock, Test) { |
| auto waiter1 = CreatePool(1); |
| auto waiter2 = CreatePool(1); |
| waiter1->Schedule([this] { this->Waiter1(); }); |
| waiter2->Schedule([this] { this->Waiter2(); }); |
|
|
| |
| absl::SleepFor(absl::Milliseconds(100)); |
|
|
| |
| mu.Lock(); |
| cond1 = true; |
| if (signal_unlocked) { |
| mu.Unlock(); |
| cv.Signal(); |
| } else { |
| cv.Signal(); |
| mu.Unlock(); |
| } |
| waiter1.reset(); |
|
|
| |
| mu.Lock(); |
| cond2 = true; |
| mu.Unlock(); |
| waiter2.reset(); |
| } |
|
|
| INSTANTIATE_TEST_SUITE_P(CondVarWaitDeadlockTest, CondVarWaitDeadlock, |
| ::testing::Range(0, 8), |
| ::testing::PrintToStringParamName()); |
|
|
| |
| |
| |
| |
| |
|
|
| struct DequeueAllWakeableBugStruct { |
| absl::Mutex mu; |
| absl::Mutex mu2; |
| int unfinished_count; |
| bool done1; |
| int finished_count; |
| bool done2; |
| }; |
|
|
| |
| static void AcquireAsReader(DequeueAllWakeableBugStruct *x) { |
| x->mu.ReaderLock(); |
| x->mu2.Lock(); |
| x->unfinished_count--; |
| x->done1 = (x->unfinished_count == 0); |
| x->mu2.Unlock(); |
| |
| absl::SleepFor(absl::Seconds(2)); |
| x->mu.ReaderUnlock(); |
|
|
| x->mu2.Lock(); |
| x->finished_count--; |
| x->done2 = (x->finished_count == 0); |
| x->mu2.Unlock(); |
| } |
|
|
| |
| 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(); |
| |
| tp->Schedule(std::bind(&AcquireAsReader, &x)); |
| tp->Schedule(std::bind(&AcquireAsReader, &x)); |
| absl::SleepFor(absl::Seconds(1)); |
| x.mu.Unlock(); |
|
|
| |
| 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; |
|
|
| |
| 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); |
| } |
|
|
| |
| |
| |
| #if !defined(ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE) |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
|
|
| struct ReaderDecrementBugStruct { |
| bool cond; |
| int done; |
| absl::Mutex mu; |
|
|
| bool waiting_on_cond; |
| bool have_reader_lock; |
| bool complete; |
| absl::Mutex mu2; |
| }; |
|
|
| |
| 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; |
| } |
|
|
| |
| static bool AllDone(void *v) { |
| ReaderDecrementBugStruct *x = reinterpret_cast<ReaderDecrementBugStruct *>(v); |
| return x->done == 0; |
| } |
|
|
| |
| static void WaitForCond(ReaderDecrementBugStruct *x) { |
| absl::Mutex dummy; |
| absl::MutexLock l(&dummy); |
| x->mu.LockWhen(absl::Condition(&IsCond, x)); |
| x->done--; |
| x->mu.Unlock(); |
| } |
|
|
| |
| 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(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; |
|
|
| |
| std::thread thread1(WaitForCond, &x); |
| x.mu2.LockWhen(absl::Condition(&x.waiting_on_cond)); |
| x.mu2.Unlock(); |
|
|
| |
| std::thread thread2(GetReadLock, &x); |
| x.mu2.LockWhen(absl::Condition(&x.have_reader_lock)); |
| x.mu2.Unlock(); |
|
|
| |
| x.mu.ReaderLock(); |
| x.mu.ReaderUnlock(); |
|
|
| |
| |
| x.mu.AssertReaderHeld(); |
|
|
| |
| x.mu2.Lock(); |
| x.complete = true; |
| x.mu2.Unlock(); |
|
|
| |
| |
| x.mu.Lock(); |
| x.cond = true; |
| x.mu.Await(absl::Condition(&AllDone, &x)); |
| x.mu.Unlock(); |
|
|
| thread1.join(); |
| thread2.join(); |
| } |
| #endif |
|
|
| |
| |
| #ifdef ABSL_HAVE_THREAD_SANITIZER |
| |
| TEST(Mutex, DISABLED_LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { |
| #else |
| TEST(Mutex, LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { |
| #endif |
| for (int i = 0; i != 10; i++) { |
| |
| 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(); |
| } |
| } |
| } |
| } |
|
|
| |
| bool Equals42(int *p) { return *p == 42; } |
| bool Equals43(int *p) { return *p == 43; } |
|
|
| |
| bool ConstEquals42(const int *p) { return *p == 42; } |
| bool ConstEquals43(const int *p) { return *p == 43; } |
|
|
| |
| |
| |
| template <typename T> |
| bool TemplateEquals42(T *p) { |
| return *p == 42; |
| } |
| template <typename T> |
| bool TemplateEquals43(T *p) { |
| return *p == 43; |
| } |
|
|
| TEST(Mutex, FunctionPointerCondition) { |
| |
| int x = 42; |
| const int const_x = 42; |
|
|
| |
| EXPECT_TRUE(absl::Condition(Equals42, &x).Eval()); |
| EXPECT_FALSE(absl::Condition(Equals43, &x).Eval()); |
|
|
| |
| EXPECT_TRUE(absl::Condition(ConstEquals42, &x).Eval()); |
| EXPECT_FALSE(absl::Condition(ConstEquals43, &x).Eval()); |
|
|
| |
| EXPECT_TRUE(absl::Condition(ConstEquals42, &const_x).Eval()); |
| EXPECT_FALSE(absl::Condition(ConstEquals43, &const_x).Eval()); |
|
|
| |
| EXPECT_TRUE(absl::Condition(TemplateEquals42, &x).Eval()); |
| EXPECT_FALSE(absl::Condition(TemplateEquals43, &x).Eval()); |
|
|
| |
| EXPECT_TRUE(absl::Condition(TemplateEquals42, &const_x).Eval()); |
| EXPECT_FALSE(absl::Condition(TemplateEquals43, &const_x).Eval()); |
|
|
| |
| EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(Equals42), |
| decltype(&const_x)>::value)); |
| |
| EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(ConstEquals42), |
| decltype(&const_x)>::value)); |
| } |
|
|
| |
| |
| struct Base { |
| explicit Base(int v) : value(v) {} |
| int value; |
| }; |
| struct Derived : Base { |
| explicit Derived(int v) : Base(v) {} |
| }; |
|
|
| |
| bool BaseEquals42(Base *p) { return p->value == 42; } |
| bool BaseEquals43(Base *p) { return p->value == 43; } |
|
|
| |
| bool ConstBaseEquals42(const Base *p) { return p->value == 42; } |
| bool ConstBaseEquals43(const Base *p) { return p->value == 43; } |
|
|
| TEST(Mutex, FunctionPointerConditionWithDerivedToBaseConversion) { |
| |
| Derived derived(42); |
| const Derived const_derived(42); |
|
|
| |
| EXPECT_TRUE(absl::Condition(BaseEquals42, &derived).Eval()); |
| EXPECT_FALSE(absl::Condition(BaseEquals43, &derived).Eval()); |
|
|
| |
| EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &derived).Eval()); |
| EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &derived).Eval()); |
|
|
| |
| EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval()); |
| EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval()); |
|
|
| |
| EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval()); |
| EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval()); |
|
|
| |
| bool (*derived_pred)(const Derived *) = [](const Derived *) { return true; }; |
| EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(derived_pred), |
| Base *>::value)); |
| EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(derived_pred), |
| const Base *>::value)); |
| |
| EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(derived_pred), |
| Derived *>::value)); |
| EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(derived_pred), |
| const Derived *>::value)); |
| } |
|
|
| struct Constable { |
| bool WotsAllThisThen() const { return true; } |
| }; |
|
|
| TEST(Mutex, FunctionPointerConditionWithConstMethod) { |
| const Constable chapman; |
| EXPECT_TRUE(absl::Condition(&chapman, &Constable::WotsAllThisThen).Eval()); |
| } |
|
|
| struct True { |
| template <class... Args> |
| bool operator()(Args...) const { |
| return true; |
| } |
| }; |
|
|
| struct DerivedTrue : True {}; |
|
|
| TEST(Mutex, FunctorCondition) { |
| { |
| True f; |
| EXPECT_TRUE(absl::Condition(&f).Eval()); |
| } |
|
|
| { |
| DerivedTrue g; |
| EXPECT_TRUE(absl::Condition(&g).Eval()); |
| } |
|
|
| { |
| 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()); |
| } |
|
|
| { |
| 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()); |
| } |
|
|
| { |
| 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()); |
| } |
| } |
|
|
| TEST(Mutex, ConditionSwap) { |
| |
| bool b1 = true; |
| absl::Condition c1(&b1); |
| bool b2 = false; |
| absl::Condition c2(&b2); |
| EXPECT_TRUE(c1.Eval()); |
| EXPECT_FALSE(c2.Eval()); |
| std::swap(c1, c2); |
| EXPECT_FALSE(c1.Eval()); |
| EXPECT_TRUE(c2.Eval()); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| 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(); |
| } |
|
|
| static bool IntIsZero(int *x) { return *x == 0; } |
|
|
| |
| |
| 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; |
| int value; |
| bool done; |
| absl::Mutex mu1; |
| absl::CondVar cv; |
| |
| }; |
|
|
| static bool ConditionWithAcquire(AcquireFromConditionStruct *x) { |
| x->value++; |
|
|
| if (x->value == 2 || x->value == 3) { |
| |
| |
| |
| |
| bool always_false = false; |
| x->mu1.LockWhenWithTimeout(absl::Condition(&always_false), |
| absl::Milliseconds(100)); |
| x->mu1.Unlock(); |
| } |
| CHECK_LT(x->value, 4) << "should not be invoked a fourth time"; |
|
|
| |
| return x->value == 2 || x->value == 3; |
| } |
|
|
| static void WaitForCond2(AcquireFromConditionStruct *x) { |
| |
| x->mu0.LockWhen(absl::Condition(&ConditionWithAcquire, x)); |
| x->done = true; |
| x->mu0.Unlock(); |
| } |
|
|
| |
| TEST(Mutex, AcquireFromCondition) { |
| auto tp = CreateDefaultPool(); |
|
|
| AcquireFromConditionStruct x; |
| x.value = 0; |
| x.done = false; |
| tp->Schedule( |
| std::bind(&WaitForCond2, &x)); |
| |
| |
| absl::SleepFor(absl::Milliseconds(500)); |
|
|
| x.mu0.Lock(); |
| x.cv.WaitWithTimeout(&x.mu0, absl::Milliseconds(500)); |
| |
| |
|
|
| x.mu0.Unlock(); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
|
|
| x.mu0.LockWhen(absl::Condition(&x.done)); |
| x.mu0.Unlock(); |
| } |
|
|
| TEST(Mutex, DeadlockDetector) { |
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); |
|
|
| |
| absl::Mutex m1; |
| absl::Mutex m2; |
| absl::Mutex m3; |
| absl::Mutex m4; |
|
|
| m1.Lock(); |
| m2.Lock(); |
| m3.Lock(); |
| m3.Unlock(); |
| m2.Unlock(); |
| |
| m1.ForgetDeadlockInfo(); |
| m2.Lock(); |
| m3.Lock(); |
| m4.Lock(); |
| m3.Unlock(); |
| m2.Unlock(); |
| m4.Unlock(); |
| m1.Unlock(); |
| } |
|
|
| |
| |
| |
| 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 |
| |
| TEST(Mutex, DISABLED_DeadlockDetectorBazelWarning) { |
| #else |
| TEST(Mutex, DeadlockDetectorBazelWarning) { |
| #endif |
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kReport); |
|
|
| |
| |
| ScopedDisableBazelTestWarnings disable_bazel_test_warnings; |
|
|
| absl::Mutex mu0; |
| absl::Mutex mu1; |
| bool got_mu0 = mu0.TryLock(); |
| mu1.Lock(); |
| if (got_mu0) { |
| mu0.Unlock(); |
| } |
| if (mu0.TryLock()) { |
| mu0.Unlock(); |
| } |
| mu0.Lock(); |
| |
| mu0.Unlock(); |
| mu1.Unlock(); |
|
|
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); |
| } |
|
|
| TEST(Mutex, DeadlockDetectorLongCycle) { |
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kReport); |
|
|
| |
| |
| ScopedDisableBazelTestWarnings disable_bazel_test_warnings; |
|
|
| |
| std::vector<absl::Mutex> mutex(100); |
| for (size_t i = 0; i != mutex.size(); i++) { |
| mutex[i].Lock(); |
| mutex[(i + 1) % mutex.size()].Lock(); |
| mutex[i].Unlock(); |
| mutex[(i + 1) % mutex.size()].Unlock(); |
| } |
|
|
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); |
| } |
|
|
| |
| |
| |
| |
| TEST(Mutex, DeadlockDetectorStressTest) ABSL_NO_THREAD_SAFETY_ANALYSIS { |
| |
| |
| |
| 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); |
| |
| 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 |
| |
| TEST(Mutex, DISABLED_DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { |
| #else |
| TEST(Mutex, DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { |
| #endif |
| |
| |
| |
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); |
| |
| absl::Mutex *a = new absl::Mutex; |
| |
| absl::Mutex b, c; |
|
|
| |
| a->Lock(); |
|
|
| |
| b.Lock(); |
| b.Unlock(); |
|
|
| |
| |
| |
| |
| |
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kIgnore); |
| delete a; |
| absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); |
|
|
| |
| |
| |
| |
| c.Lock(); |
| c.Unlock(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| static absl::Duration TimeoutTestAllowedSchedulingDelay() { |
| |
| |
| return absl::Milliseconds(150); |
| } |
|
|
| |
| |
| ABSL_MUST_USE_RESULT |
| static bool DelayIsWithinBounds(absl::Duration expected_delay, |
| absl::Duration actual_delay) { |
| bool pass = true; |
| |
| |
| |
| |
| if (actual_delay < expected_delay) { |
| LOG(WARNING) << "Actual delay " << actual_delay |
| << " was too short, expected " << expected_delay |
| << " (difference " << actual_delay - expected_delay << ")"; |
| pass = false; |
| } |
| |
| |
| |
| |
| absl::Duration tolerance = expected_delay <= absl::ZeroDuration() |
| ? absl::Milliseconds(10) |
| : TimeoutTestAllowedSchedulingDelay(); |
| if (actual_delay > expected_delay + tolerance) { |
| LOG(WARNING) << "Actual delay " << actual_delay |
| << " was too long, expected " << expected_delay |
| << " (difference " << actual_delay - expected_delay << ")"; |
| pass = false; |
| } |
| return pass; |
| } |
|
|
| |
| struct TimeoutTestParam { |
| |
| const char *from_file; |
| int from_line; |
|
|
| |
| |
| bool use_absolute_deadline; |
|
|
| |
| |
| absl::Duration wait_timeout; |
|
|
| |
| |
| |
| |
| |
| absl::Duration satisfy_condition_delay; |
|
|
| |
| |
| |
| bool expected_result; |
|
|
| |
| |
| |
| absl::Duration expected_delay; |
| }; |
|
|
| |
| 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; |
| } |
|
|
| |
| |
| |
| |
| static void RunAfterDelay(absl::Duration delay, |
| absl::synchronization_internal::ThreadPool *pool, |
| const std::function<void()> &callback) { |
| if (delay <= absl::ZeroDuration()) { |
| callback(); |
| } else if (delay != absl::InfiniteDuration()) { |
| ScheduleAfter(pool, delay, callback); |
| } |
| } |
|
|
| class TimeoutTest : public ::testing::Test, |
| public ::testing::WithParamInterface<TimeoutTestParam> {}; |
|
|
| std::vector<TimeoutTestParam> MakeTimeoutTestParamValues() { |
| |
| |
| |
| const absl::Duration finite = 3 * TimeoutTestAllowedSchedulingDelay(); |
| const absl::Duration never = absl::InfiniteDuration(); |
| const absl::Duration negative = -absl::InfiniteDuration(); |
| const absl::Duration immediate = absl::ZeroDuration(); |
|
|
| |
| |
| std::vector<TimeoutTestParam> values; |
| for (bool use_absolute_deadline : {false, true}) { |
| |
| |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| negative, |
| immediate, |
| true, |
| immediate, |
| }); |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| negative, |
| finite, |
| false, |
| immediate |
| }); |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| negative, |
| never, |
| false, |
| immediate |
| }); |
|
|
| |
| |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| never, |
| immediate, |
| true, |
| immediate |
| }); |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| never, |
| finite, |
| true, |
| finite, |
| }); |
|
|
| |
| |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| never, |
| immediate, |
| true, |
| immediate |
| }); |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| finite * 2, |
| finite, |
| true, |
| finite |
| }); |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| finite, |
| finite * 2, |
| false, |
| finite |
| }); |
|
|
| |
| values.push_back(TimeoutTestParam{ |
| __FILE__, __LINE__, use_absolute_deadline, |
| finite, |
| never, |
| false, |
| finite |
| }); |
| } |
| return values; |
| } |
|
|
| |
| INSTANTIATE_TEST_SUITE_P(All, TimeoutTest, |
| testing::ValuesIn(MakeTimeoutTestParamValues())); |
|
|
| TEST_P(TimeoutTest, Await) { |
| const TimeoutTestParam params = GetParam(); |
| LOG(INFO) << "Params: " << params; |
|
|
| |
| |
| |
| for (int attempt = 1;; ++attempt) { |
| LOG(INFO) << "Attempt " << attempt; |
|
|
| absl::Mutex mu; |
| bool value = false; |
|
|
| 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(); |
| LOG(INFO) << "Params: " << params; |
|
|
| |
| |
| |
| for (int attempt = 1;; ++attempt) { |
| LOG(INFO) << "Attempt " << attempt; |
|
|
| absl::Mutex mu; |
| bool value = false; |
|
|
| 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(); |
| LOG(INFO) << "Params: " << params; |
|
|
| |
| |
| |
| for (int attempt = 0;; ++attempt) { |
| LOG(INFO) << "Attempt " << attempt; |
|
|
| absl::Mutex mu; |
| bool value = false; |
|
|
| 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(); |
| LOG(INFO) << "Params: " << params; |
|
|
| |
| |
| |
| for (int attempt = 0;; ++attempt) { |
| LOG(INFO) << "Attempt " << attempt; |
|
|
| absl::Mutex mu; |
| bool value = false; |
| absl::CondVar cv; |
|
|
| 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; |
| } |
| timeout = deadline - absl::Now(); |
| } |
| bool result = value; |
|
|
| if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { |
| EXPECT_EQ(params.expected_result, result); |
| break; |
| } |
| } |
| } |
|
|
| TEST(Mutex, Logging) { |
| |
| 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(); |
| } |
|
|
| TEST(Mutex, LoggingAddressReuse) { |
| |
| ScopedInvariantDebugging scoped_debugging; |
| alignas(absl::Mutex) char storage[sizeof(absl::Mutex)]; |
| auto invariant = |
| +[](void *alive) { EXPECT_TRUE(*static_cast<bool *>(alive)); }; |
| constexpr size_t kIters = 10; |
| bool alive[kIters] = {}; |
| for (size_t i = 0; i < kIters; ++i) { |
| absl::Mutex *mu = new (storage) absl::Mutex; |
| alive[i] = true; |
| mu->EnableDebugLog("Mutex"); |
| mu->EnableInvariantDebugging(invariant, &alive[i]); |
| mu->Lock(); |
| mu->Unlock(); |
| mu->~Mutex(); |
| alive[i] = false; |
| } |
| } |
|
|
| TEST(Mutex, LoggingBankrupcy) { |
| |
| ScopedInvariantDebugging scoped_debugging; |
| std::vector<absl::Mutex> mus(1 << 20); |
| for (auto &mu : mus) { |
| mu.EnableDebugLog("Mutex"); |
| } |
| } |
|
|
| TEST(Mutex, SynchEventRace) { |
| |
| |
| ScopedInvariantDebugging scoped_debugging; |
| std::vector<std::thread> threads; |
| for (size_t i = 0; i < 5; i++) { |
| threads.emplace_back([&] { |
| for (size_t j = 0; j < (1 << 17); j++) { |
| { |
| absl::Mutex mu; |
| mu.EnableInvariantDebugging([](void *) {}, nullptr); |
| mu.Lock(); |
| mu.Unlock(); |
| } |
| { |
| absl::Mutex mu; |
| mu.EnableDebugLog("Mutex"); |
| } |
| } |
| }); |
| } |
| for (auto &thread : threads) { |
| thread.join(); |
| } |
| } |
|
|
| |
|
|
| |
| static std::vector<int> AllThreadCountValues() { |
| if (kExtendedTest) { |
| return {2, 4, 8, 10, 16, 20, 24, 30, 32}; |
| } |
| return {2, 4, 10}; |
| } |
|
|
| |
| class MutexVariableThreadCountTest : public ::testing::TestWithParam<int> {}; |
|
|
| |
| INSTANTIATE_TEST_SUITE_P(ThreadCounts, MutexVariableThreadCountTest, |
| ::testing::ValuesIn(AllThreadCountValues()), |
| ::testing::PrintToStringParamName()); |
|
|
| |
| |
| static int ScaleIterations(int x) { |
| |
| |
| |
| #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; |
| int iterations = 200000; |
| int operations = iterations; |
| EXPECT_EQ(RunTest(&TestSignal, threads, iterations, operations), operations); |
| } |
|
|
| TEST(Mutex, Timed) { |
| int threads = 10; |
| int iterations = 1000; |
| int operations = iterations; |
| EXPECT_EQ(RunTest(&TestCVTimeout, threads, iterations, operations), |
| operations); |
| } |
|
|
| TEST(Mutex, CVTime) { |
| int threads = 10; |
| int iterations = 1; |
| EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), threads * iterations); |
| } |
|
|
| TEST(Mutex, MuTime) { |
| int threads = 10; |
| int iterations = 1; |
| EXPECT_EQ(RunTest(&TestMuTime, threads, iterations, 1), threads * iterations); |
| } |
|
|
| TEST(Mutex, SignalExitedThread) { |
| |
| |
| #if defined(__wasm__) || defined(__asmjs__) |
| constexpr int kThreads = 1; |
| #else |
| constexpr int kThreads = 100; |
| #endif |
| std::vector<std::thread> top; |
| for (unsigned i = 0; i < 2 * std::thread::hardware_concurrency(); i++) { |
| top.emplace_back([&]() { |
| for (int i = 0; i < kThreads; i++) { |
| absl::Mutex mu; |
| std::thread t([&]() { |
| mu.Lock(); |
| mu.Unlock(); |
| }); |
| mu.Lock(); |
| mu.Unlock(); |
| t.join(); |
| } |
| }); |
| } |
| for (auto &th : top) th.join(); |
| } |
|
|
| TEST(Mutex, WriterPriority) { |
| absl::Mutex mu; |
| bool wrote = false; |
| std::atomic<bool> saw_wrote{false}; |
| auto readfunc = [&]() { |
| for (size_t i = 0; i < 10; ++i) { |
| absl::ReaderMutexLock lock(&mu); |
| if (wrote) { |
| saw_wrote = true; |
| break; |
| } |
| absl::SleepFor(absl::Seconds(1)); |
| } |
| }; |
| std::thread t1(readfunc); |
| absl::SleepFor(absl::Milliseconds(500)); |
| std::thread t2(readfunc); |
| |
| |
| std::thread t3([&]() { |
| |
| absl::MutexLock lock(&mu); |
| wrote = true; |
| }); |
| t1.join(); |
| t2.join(); |
| t3.join(); |
| EXPECT_TRUE(saw_wrote.load()); |
| } |
|
|
| #ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM |
| TEST(Mutex, CondVarPriority) { |
| |
| |
| int err = 0; |
| sched_param param; |
| param.sched_priority = 7; |
| std::thread test([&]() { |
| err = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); |
| }); |
| test.join(); |
| if (err) { |
| |
| GTEST_SKIP() << "failed to set priority: " << strerror(err); |
| } |
| absl::Mutex mu; |
| absl::CondVar cv; |
| bool locked = false; |
| bool notified = false; |
| bool waiting = false; |
| bool morph = false; |
| std::thread th([&]() { |
| EXPECT_EQ(0, pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m)); |
| mu.Lock(); |
| locked = true; |
| mu.Await(absl::Condition(¬ified)); |
| mu.Unlock(); |
| EXPECT_EQ(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() |
| ->per_thread_synch.priority, |
| param.sched_priority); |
| mu.Lock(); |
| mu.Await(absl::Condition(&waiting)); |
| morph = true; |
| absl::SleepFor(absl::Seconds(1)); |
| cv.Signal(); |
| mu.Unlock(); |
| }); |
| mu.Lock(); |
| mu.Await(absl::Condition(&locked)); |
| notified = true; |
| mu.Unlock(); |
| mu.Lock(); |
| waiting = true; |
| while (!morph) { |
| cv.Wait(&mu); |
| } |
| mu.Unlock(); |
| th.join(); |
| EXPECT_NE(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() |
| ->per_thread_synch.priority, |
| param.sched_priority); |
| } |
| #endif |
|
|
| TEST(Mutex, LockWhenWithTimeoutResult) { |
| |
| |
| absl::Mutex mu; |
| const bool kAlwaysTrue = true, kAlwaysFalse = false; |
| const absl::Condition kTrueCond(&kAlwaysTrue), kFalseCond(&kAlwaysFalse); |
| EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1))); |
| mu.Unlock(); |
| EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1))); |
| EXPECT_TRUE(mu.AwaitWithTimeout(kTrueCond, absl::Milliseconds(1))); |
| EXPECT_FALSE(mu.AwaitWithTimeout(kFalseCond, absl::Milliseconds(1))); |
| std::thread th1([&]() { |
| EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1))); |
| mu.Unlock(); |
| }); |
| std::thread th2([&]() { |
| EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1))); |
| mu.Unlock(); |
| }); |
| absl::SleepFor(absl::Milliseconds(100)); |
| mu.Unlock(); |
| th1.join(); |
| th2.join(); |
| } |
|
|
| } |
|
|