Nagram/TMessagesProj/jni/voip/webrtc/absl/synchronization/mutex_test.cc

1708 lines
54 KiB
C++
Raw Normal View History

2022-03-11 16:49:54 +00:00
// 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 <type_traits>
#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) ABSL_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();
}
}
}
}
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());
}
}
// --------------------------------------------------------
// 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();
}
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) ABSL_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 &param) {
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 &param) {
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