#include "absl/synchronization/mutex.h"
#ifdef _WIN32
#include <windows.h>
#endif
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <functional>
#include <memory>
#include <mutex>
#include <random>
#include <shared_mutex>
#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/random/random.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.try_lock());
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.lock_shared();
while (!cond1) {
cv.Wait(&mu);
}
mu.unlock_shared();
} else {
mu.lock();
while (!cond1) {
cv.Wait(&mu);
}
mu.unlock();
}
}
void Waiter2() {
if (read_lock2) {
mu.ReaderLockWhen(absl::Condition(&cond2));
mu.unlock_shared();
} 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.lock_shared();
x->mu2.lock();
x->unfinished_count--;
x->done1 = (x->unfinished_count == 0);
x->mu2.unlock();
absl::SleepFor(absl::Seconds(2));
x->mu.unlock_shared();
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.lock_shared();
x->mu2.lock();
x->have_reader_lock = true;
x->mu2.Await(absl::Condition(&x->complete));
x->mu2.unlock();
x->mu.unlock_shared();
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.lock_shared();
x.mu.unlock_shared();
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].lock();
} else {
mu[j].lock_shared();
}
}
}
}
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());
}
#ifdef __cpp_explicit_this_parameter
struct TrueViaDeducingThis {
template <class This, class... Args>
bool operator()(this const This&, Args...) {
return true;
}
};
TEST(Mutex, FunctorConditionDeducingThis) {
TrueViaDeducingThis f;
EXPECT_TRUE(absl::Condition(&f).Eval());
}
#endif
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) {
absl::InsecureBitGen gen;
std::uniform_int_distribution<int> random_millis(0, 15);
mu->lock_shared();
while (*running == 3) {
absl::SleepFor(absl::Milliseconds(random_millis(gen)));
cv->WaitWithTimeout(mu, absl::Milliseconds(random_millis(gen)));
}
mu->unlock_shared();
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.try_lock();
mu1.lock(); if (got_mu0) {
mu0.unlock();
}
if (mu0.try_lock()) { 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);
}
[[nodiscard]]
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.unlock_shared();
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.lock_shared();
logged_mutex.unlock_shared();
logged_mutex.lock();
logged_mutex.unlock();
logged_cv.Signal();
logged_cv.SignalAll();
}
TEST(Mutex, LoggingAddressReuse) {
ScopedInvariantDebugging scoped_debugging;
alignas(absl::Mutex) unsigned 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();
}
TEST(Mutex, ScopedLock) {
absl::Mutex mu;
{
std::scoped_lock l(mu);
}
{
std::shared_lock l(mu);
EXPECT_TRUE(l.owns_lock());
}
}
}