diff options
Diffstat (limited to 'abseil-cpp/absl/synchronization/mutex_test.cc')
-rw-r--r-- | abseil-cpp/absl/synchronization/mutex_test.cc | 432 |
1 files changed, 326 insertions, 106 deletions
diff --git a/abseil-cpp/absl/synchronization/mutex_test.cc b/abseil-cpp/absl/synchronization/mutex_test.cc index 16fc905..b585c34 100644 --- a/abseil-cpp/absl/synchronization/mutex_test.cc +++ b/abseil-cpp/absl/synchronization/mutex_test.cc @@ -26,13 +26,15 @@ #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/log/check.h" +#include "absl/log/log.h" #include "absl/memory/memory.h" #include "absl/synchronization/internal/thread_pool.h" #include "absl/time/clock.h" @@ -86,7 +88,7 @@ static void SetInvariantChecked(bool new_value) { static void CheckSumG0G1(void *v) { TestContext *cxt = static_cast<TestContext *>(v); - ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in CheckSumG0G1"); + CHECK_EQ(cxt->g0, -cxt->g1) << "Error in CheckSumG0G1"; SetInvariantChecked(true); } @@ -131,7 +133,7 @@ static void TestRW(TestContext *cxt, int c) { } else { for (int i = 0; i != cxt->iterations; i++) { absl::ReaderMutexLock l(&cxt->mu); - ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in TestRW"); + CHECK_EQ(cxt->g0, -cxt->g1) << "Error in TestRW"; cxt->mu.AssertReaderHeld(); } } @@ -156,7 +158,7 @@ static void TestAwait(TestContext *cxt, int c) { cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { cxt->mu.Await(absl::Condition(&mc, &MyContext::MyTurn)); - ABSL_RAW_CHECK(mc.MyTurn(), "Error in TestAwait"); + CHECK(mc.MyTurn()) << "Error in TestAwait"; cxt->mu.AssertHeld(); if (cxt->g0 < cxt->iterations) { int a = cxt->g0 + 1; @@ -184,7 +186,7 @@ static void TestSignalAll(TestContext *cxt, int c) { } static void TestSignal(TestContext *cxt, int c) { - ABSL_RAW_CHECK(cxt->threads == 2, "TestSignal should use 2 threads"); + CHECK_EQ(cxt->threads, 2) << "TestSignal should use 2 threads"; int target = c; absl::MutexLock l(&cxt->mu); cxt->mu.AssertHeld(); @@ -221,8 +223,8 @@ static void TestCVTimeout(TestContext *cxt, int c) { 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"); + 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); @@ -233,26 +235,24 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { 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"); + 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"); + 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 { - ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), - "TestTime failed"); + 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"); + CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) + << "TestTime failed"; cxt->g0++; if (use_cv) { cxt->cv.Signal(); @@ -262,26 +262,24 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { 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"); + 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"); + 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 { - ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), - "TestTime failed"); + 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"); + CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) + << "TestTime failed"; if (use_cv) { cxt->cv.SignalAll(); } @@ -290,13 +288,13 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { 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"); + 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"); + 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); @@ -304,14 +302,12 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { 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"); + 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"); + CHECK(absl::Seconds(0.4) <= elapsed && elapsed <= absl::Seconds(0.9)) + << "TestTime failed"; cxt->g0++; } else if (c == 2) { absl::MutexLock l(&cxt->mu); @@ -320,8 +316,8 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(100)); } } else { - ABSL_RAW_CHECK(cxt->mu.AwaitWithTimeout(g0ge2, absl::Seconds(100)), - "TestTime failed"); + CHECK(cxt->mu.AwaitWithTimeout(g0ge2, absl::Seconds(100))) + << "TestTime failed"; } cxt->g0++; } else { @@ -342,7 +338,7 @@ 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) { + const std::function<void(int)> &cb) { mu->Lock(); int c = (*c0)++; mu->Unlock(); @@ -365,9 +361,9 @@ static int RunTestCommon(TestContext *cxt, void (*test)(TestContext *cxt, int), 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)))); + 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) { @@ -398,7 +394,7 @@ static int RunTestWithInvariantDebugging(void (*test)(TestContext *cxt, int), TestContext cxt; cxt.mu.EnableInvariantDebugging(invariant, &cxt); int ret = RunTestCommon(&cxt, test, threads, iterations, operations); - ABSL_RAW_CHECK(GetInvariantChecked(), "Invariant not checked"); + CHECK(GetInvariantChecked()) << "Invariant not checked"; absl::EnableMutexInvariantDebugging(false); // Restore. return ret; } @@ -681,14 +677,14 @@ struct LockWhenTestStruct { bool waiting = false; }; -static bool LockWhenTestIsCond(LockWhenTestStruct* s) { +static bool LockWhenTestIsCond(LockWhenTestStruct *s) { s->mu2.Lock(); s->waiting = true; s->mu2.Unlock(); return s->cond; } -static void LockWhenTestWaitForIsCond(LockWhenTestStruct* s) { +static void LockWhenTestWaitForIsCond(LockWhenTestStruct *s) { s->mu1.LockWhen(absl::Condition(&LockWhenTestIsCond, s)); s->mu1.Unlock(); } @@ -707,6 +703,40 @@ TEST(Mutex, LockWhen) { 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. @@ -818,7 +848,7 @@ TEST(Mutex, MutexReaderDecrementBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { // held and then destroyed (w/o unlocking). #ifdef ABSL_HAVE_THREAD_SANITIZER // TSAN reports errors when locked Mutexes are destroyed. -TEST(Mutex, DISABLED_LockedMutexDestructionBug) NO_THREAD_SAFETY_ANALYSIS { +TEST(Mutex, DISABLED_LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { #else TEST(Mutex, LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { #endif @@ -836,31 +866,109 @@ TEST(Mutex, LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { } } -// -------------------------------------------------------- -// 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.) +// Some functions taking pointers to non-const. +bool Equals42(int *p) { return *p == 42; } +bool Equals43(int *p) { return *p == 43; } -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(); +// Some functions taking pointers to const. +bool ConstEquals42(const int *p) { return *p == 42; } +bool ConstEquals43(const int *p) { return *p == 43; } + +// Some function templates taking pointers. Note it's possible for `T` to be +// deduced as non-const or const, which creates the potential for ambiguity, +// but which the implementation is careful to avoid. +template <typename T> +bool TemplateEquals42(T *p) { + return *p == 42; +} +template <typename T> +bool TemplateEquals43(T *p) { + return *p == 43; +} + +TEST(Mutex, FunctionPointerCondition) { + // Some arguments. + int x = 42; + const int const_x = 42; + + // Parameter non-const, argument non-const. + EXPECT_TRUE(absl::Condition(Equals42, &x).Eval()); + EXPECT_FALSE(absl::Condition(Equals43, &x).Eval()); + + // Parameter const, argument non-const. + EXPECT_TRUE(absl::Condition(ConstEquals42, &x).Eval()); + EXPECT_FALSE(absl::Condition(ConstEquals43, &x).Eval()); + + // Parameter const, argument const. + EXPECT_TRUE(absl::Condition(ConstEquals42, &const_x).Eval()); + EXPECT_FALSE(absl::Condition(ConstEquals43, &const_x).Eval()); + + // Parameter type deduced, argument non-const. + EXPECT_TRUE(absl::Condition(TemplateEquals42, &x).Eval()); + EXPECT_FALSE(absl::Condition(TemplateEquals43, &x).Eval()); + + // Parameter type deduced, argument const. + EXPECT_TRUE(absl::Condition(TemplateEquals42, &const_x).Eval()); + EXPECT_FALSE(absl::Condition(TemplateEquals43, &const_x).Eval()); + + // Parameter non-const, argument const is not well-formed. + EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(Equals42), + decltype(&const_x)>::value)); + // Validate use of is_constructible by contrasting to a well-formed case. + EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(ConstEquals42), + decltype(&const_x)>::value)); +} + +// Example base and derived class for use in predicates and test below. Not a +// particularly realistic example, but it suffices for testing purposes. +struct Base { + explicit Base(int v) : value(v) {} + int value; +}; +struct Derived : Base { + explicit Derived(int v) : Base(v) {} +}; + +// Some functions taking pointer to non-const `Base`. +bool BaseEquals42(Base *p) { return p->value == 42; } +bool BaseEquals43(Base *p) { return p->value == 43; } + +// Some functions taking pointer to const `Base`. +bool ConstBaseEquals42(const Base *p) { return p->value == 42; } +bool ConstBaseEquals43(const Base *p) { return p->value == 43; } + +TEST(Mutex, FunctionPointerConditionWithDerivedToBaseConversion) { + // Some arguments. + Derived derived(42); + const Derived const_derived(42); + + // Parameter non-const base, argument derived non-const. + EXPECT_TRUE(absl::Condition(BaseEquals42, &derived).Eval()); + EXPECT_FALSE(absl::Condition(BaseEquals43, &derived).Eval()); + + // Parameter const base, argument derived non-const. + EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &derived).Eval()); + EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &derived).Eval()); + + // Parameter const base, argument derived const. + EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval()); + EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval()); + + // Parameter const base, argument derived const. + EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval()); + EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval()); + + // Parameter derived, argument base is not well-formed. + 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)); + // Validate use of is_constructible by contrasting to well-formed cases. + 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 True { @@ -911,6 +1019,33 @@ TEST(Mutex, FunctorCondition) { } } +// -------------------------------------------------------- +// 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 @@ -952,7 +1087,7 @@ static bool ConditionWithAcquire(AcquireFromConditionStruct *x) { absl::Milliseconds(100)); x->mu1.Unlock(); } - ABSL_RAW_CHECK(x->value < 4, "should not be invoked a fourth time"); + CHECK_LT(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; @@ -1002,9 +1137,6 @@ TEST(Mutex, AcquireFromCondition) { x.mu0.Unlock(); } -// The deadlock detector is not part of non-prod builds, so do not test it. -#if !defined(ABSL_INTERNAL_USE_NONPROD_MUTEX) - TEST(Mutex, DeadlockDetector) { absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); @@ -1098,6 +1230,25 @@ TEST(Mutex, DeadlockDetectorBazelWarning) { absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); } +TEST(Mutex, DeadlockDetectorLongCycle) { + absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kReport); + + // This test generates a warning if it passes, and crashes otherwise. + // Cause bazel to ignore the warning. + ScopedDisableBazelTestWarnings disable_bazel_test_warnings; + + // Check that we survive a deadlock with a lock cycle. + 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); +} + // 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 @@ -1122,7 +1273,7 @@ TEST(Mutex, DeadlockDetectorStressTest) ABSL_NO_THREAD_SAFETY_ANALYSIS { #ifdef ABSL_HAVE_THREAD_SANITIZER // TSAN reports errors when locked Mutexes are destroyed. -TEST(Mutex, DISABLED_DeadlockIdBug) NO_THREAD_SAFETY_ANALYSIS { +TEST(Mutex, DISABLED_DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { #else TEST(Mutex, DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { #endif @@ -1158,7 +1309,6 @@ TEST(Mutex, DeadlockIdBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { c.Lock(); c.Unlock(); } -#endif // !defined(ABSL_INTERNAL_USE_NONPROD_MUTEX) // -------------------------------------------------------- // Test for timeouts/deadlines on condition waits that are specified using @@ -1184,11 +1334,9 @@ static bool DelayIsWithinBounds(absl::Duration expected_delay, // 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()); + LOG(WARNING) << "Actual delay " << actual_delay + << " was too short, expected " << expected_delay + << " (difference " << actual_delay - expected_delay << ")"; pass = false; } // If the expected delay is <= zero then allow a small error tolerance, since @@ -1199,11 +1347,9 @@ static bool DelayIsWithinBounds(absl::Duration expected_delay, ? 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()); + LOG(WARNING) << "Actual delay " << actual_delay + << " was too long, expected " << expected_delay + << " (difference " << actual_delay - expected_delay << ")"; pass = false; } return pass; @@ -1253,12 +1399,6 @@ std::ostream &operator<<(std::ostream &os, const TimeoutTestParam ¶m) { << " expected_delay: " << param.expected_delay; } -std::string FormatString(const TimeoutTestParam ¶m) { - std::ostringstream os; - os << param; - return os.str(); -} - // Like `thread::Executor::ScheduleAt` except: // a) Delays zero or negative are executed immediately in the current thread. // b) Infinite delays are never scheduled. @@ -1388,13 +1528,13 @@ INSTANTIATE_TEST_SUITE_P(All, TimeoutTest, TEST_P(TimeoutTest, Await) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // 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); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1422,13 +1562,13 @@ TEST_P(TimeoutTest, Await) { TEST_P(TimeoutTest, LockWhen) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // 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); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1457,13 +1597,13 @@ TEST_P(TimeoutTest, LockWhen) { TEST_P(TimeoutTest, ReaderLockWhen) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // 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); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1493,13 +1633,13 @@ TEST_P(TimeoutTest, ReaderLockWhen) { TEST_P(TimeoutTest, Wait) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // 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); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1663,8 +1803,7 @@ TEST(Mutex, Timed) { 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); + EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), threads * iterations); } TEST(Mutex, MuTime) { @@ -1673,4 +1812,85 @@ TEST(Mutex, MuTime) { EXPECT_EQ(RunTest(&TestMuTime, threads, iterations, 1), threads * iterations); } +TEST(Mutex, SignalExitedThread) { + // The test may expose a race when Mutex::Unlock signals a thread + // that has already exited. +#if defined(__wasm__) || defined(__asmjs__) + constexpr int kThreads = 1; // OOMs under WASM +#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); + // Note: this test guards against a bug that was related to an uninit + // PerThreadSynch::priority, so the writer intentionally runs on a new thread. + std::thread t3([&]() { + // The writer should be able squeeze between the two alternating readers. + absl::MutexLock lock(&mu); + wrote = true; + }); + t1.join(); + t2.join(); + t3.join(); + EXPECT_TRUE(saw_wrote.load()); +} + +TEST(Mutex, LockWhenWithTimeoutResult) { + // Check various corner cases for Await/LockWhen return value + // with always true/always false conditions. + 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(); +} + } // namespace |