123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707 |
- // 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 ¶m) {
- return os << "from: " << param.from_file << ":" << param.from_line
- << " use_absolute_deadline: "
- << (param.use_absolute_deadline ? "true" : "false")
- << " wait_timeout: " << param.wait_timeout
- << " satisfy_condition_delay: " << param.satisfy_condition_delay
- << " expected_result: "
- << (param.expected_result ? "true" : "false")
- << " expected_delay: " << param.expected_delay;
- }
- std::string FormatString(const TimeoutTestParam ¶m) {
- std::ostringstream os;
- os << param;
- return os.str();
- }
- // Like `thread::Executor::ScheduleAt` except:
- // a) Delays zero or negative are executed immediately in the current thread.
- // b) Infinite delays are never scheduled.
- // c) Calls this test's `ScheduleAt` helper instead of using `pool` directly.
- static void RunAfterDelay(absl::Duration delay,
- absl::synchronization_internal::ThreadPool *pool,
- const std::function<void()> &callback) {
- if (delay <= absl::ZeroDuration()) {
- callback(); // immediate
- } else if (delay != absl::InfiniteDuration()) {
- ScheduleAfter(pool, delay, callback);
- }
- }
- class TimeoutTest : public ::testing::Test,
- public ::testing::WithParamInterface<TimeoutTestParam> {};
- std::vector<TimeoutTestParam> MakeTimeoutTestParamValues() {
- // The `finite` delay is a finite, relatively short, delay. We make it larger
- // than our allowed scheduling delay (slop factor) to avoid confusion when
- // diagnosing test failures. The other constants here have clear meanings.
- const absl::Duration finite = 3 * TimeoutTestAllowedSchedulingDelay();
- const absl::Duration never = absl::InfiniteDuration();
- const absl::Duration negative = -absl::InfiniteDuration();
- const absl::Duration immediate = absl::ZeroDuration();
- // Every test case is run twice; once using the absolute deadline API and once
- // using the relative timeout API.
- std::vector<TimeoutTestParam> values;
- for (bool use_absolute_deadline : {false, true}) {
- // Tests with a negative timeout (deadline in the past), which should
- // immediately return current state of the condition.
- // The condition is already true:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- negative, // wait_timeout
- immediate, // satisfy_condition_delay
- true, // expected_result
- immediate, // expected_delay
- });
- // The condition becomes true, but the timeout has already expired:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- negative, // wait_timeout
- finite, // satisfy_condition_delay
- false, // expected_result
- immediate // expected_delay
- });
- // The condition never becomes true:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- negative, // wait_timeout
- never, // satisfy_condition_delay
- false, // expected_result
- immediate // expected_delay
- });
- // Tests with an infinite timeout (deadline in the infinite future), which
- // should only return when the condition becomes true.
- // The condition is already true:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- never, // wait_timeout
- immediate, // satisfy_condition_delay
- true, // expected_result
- immediate // expected_delay
- });
- // The condition becomes true before the (infinite) expiry:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- never, // wait_timeout
- finite, // satisfy_condition_delay
- true, // expected_result
- finite, // expected_delay
- });
- // Tests with a (small) finite timeout (deadline soon), with the condition
- // becoming true both before and after its expiry.
- // The condition is already true:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- never, // wait_timeout
- immediate, // satisfy_condition_delay
- true, // expected_result
- immediate // expected_delay
- });
- // The condition becomes true before the expiry:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- finite * 2, // wait_timeout
- finite, // satisfy_condition_delay
- true, // expected_result
- finite // expected_delay
- });
- // The condition becomes true, but the timeout has already expired:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- finite, // wait_timeout
- finite * 2, // satisfy_condition_delay
- false, // expected_result
- finite // expected_delay
- });
- // The condition never becomes true:
- values.push_back(TimeoutTestParam{
- __FILE__, __LINE__, use_absolute_deadline,
- finite, // wait_timeout
- never, // satisfy_condition_delay
- false, // expected_result
- finite // expected_delay
- });
- }
- return values;
- }
- // Instantiate `TimeoutTest` with `MakeTimeoutTestParamValues()`.
- INSTANTIATE_TEST_SUITE_P(All, TimeoutTest,
- testing::ValuesIn(MakeTimeoutTestParamValues()));
- TEST_P(TimeoutTest, Await) {
- const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
- // Because this test asserts bounds on scheduling delays it is flaky. To
- // compensate it loops forever until it passes. Failures express as test
- // timeouts, in which case the test log can be used to diagnose the issue.
- for (int attempt = 1;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
- absl::Mutex mu;
- bool value = false; // condition value (under mu)
- std::unique_ptr<absl::synchronization_internal::ThreadPool> pool =
- CreateDefaultPool();
- RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] {
- absl::MutexLock l(&mu);
- value = true;
- });
- absl::MutexLock lock(&mu);
- absl::Time start_time = absl::Now();
- absl::Condition cond(&value);
- bool result =
- params.use_absolute_deadline
- ? mu.AwaitWithDeadline(cond, start_time + params.wait_timeout)
- : mu.AwaitWithTimeout(cond, params.wait_timeout);
- if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) {
- EXPECT_EQ(params.expected_result, result);
- break;
- }
- }
- }
- TEST_P(TimeoutTest, LockWhen) {
- const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
- // Because this test asserts bounds on scheduling delays it is flaky. To
- // compensate it loops forever until it passes. Failures express as test
- // timeouts, in which case the test log can be used to diagnose the issue.
- for (int attempt = 1;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
- absl::Mutex mu;
- bool value = false; // condition value (under mu)
- std::unique_ptr<absl::synchronization_internal::ThreadPool> pool =
- CreateDefaultPool();
- RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] {
- absl::MutexLock l(&mu);
- value = true;
- });
- absl::Time start_time = absl::Now();
- absl::Condition cond(&value);
- bool result =
- params.use_absolute_deadline
- ? mu.LockWhenWithDeadline(cond, start_time + params.wait_timeout)
- : mu.LockWhenWithTimeout(cond, params.wait_timeout);
- mu.Unlock();
- if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) {
- EXPECT_EQ(params.expected_result, result);
- break;
- }
- }
- }
- TEST_P(TimeoutTest, ReaderLockWhen) {
- const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
- // Because this test asserts bounds on scheduling delays it is flaky. To
- // compensate it loops forever until it passes. Failures express as test
- // timeouts, in which case the test log can be used to diagnose the issue.
- for (int attempt = 0;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
- absl::Mutex mu;
- bool value = false; // condition value (under mu)
- std::unique_ptr<absl::synchronization_internal::ThreadPool> pool =
- CreateDefaultPool();
- RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] {
- absl::MutexLock l(&mu);
- value = true;
- });
- absl::Time start_time = absl::Now();
- bool result =
- params.use_absolute_deadline
- ? mu.ReaderLockWhenWithDeadline(absl::Condition(&value),
- start_time + params.wait_timeout)
- : mu.ReaderLockWhenWithTimeout(absl::Condition(&value),
- params.wait_timeout);
- mu.ReaderUnlock();
- if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) {
- EXPECT_EQ(params.expected_result, result);
- break;
- }
- }
- }
- TEST_P(TimeoutTest, Wait) {
- const TimeoutTestParam params = GetParam();
- ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str());
- // Because this test asserts bounds on scheduling delays it is flaky. To
- // compensate it loops forever until it passes. Failures express as test
- // timeouts, in which case the test log can be used to diagnose the issue.
- for (int attempt = 0;; ++attempt) {
- ABSL_RAW_LOG(INFO, "Attempt %d", attempt);
- absl::Mutex mu;
- bool value = false; // condition value (under mu)
- absl::CondVar cv; // signals a change of `value`
- std::unique_ptr<absl::synchronization_internal::ThreadPool> pool =
- CreateDefaultPool();
- RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] {
- absl::MutexLock l(&mu);
- value = true;
- cv.Signal();
- });
- absl::MutexLock lock(&mu);
- absl::Time start_time = absl::Now();
- absl::Duration timeout = params.wait_timeout;
- absl::Time deadline = start_time + timeout;
- while (!value) {
- if (params.use_absolute_deadline ? cv.WaitWithDeadline(&mu, deadline)
- : cv.WaitWithTimeout(&mu, timeout)) {
- break; // deadline/timeout exceeded
- }
- timeout = deadline - absl::Now(); // recompute
- }
- bool result = value; // note: `mu` is still held
- if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) {
- EXPECT_EQ(params.expected_result, result);
- break;
- }
- }
- }
- TEST(Mutex, Logging) {
- // Allow user to look at logging output
- absl::Mutex logged_mutex;
- logged_mutex.EnableDebugLog("fido_mutex");
- absl::CondVar logged_cv;
- logged_cv.EnableDebugLog("rover_cv");
- logged_mutex.Lock();
- logged_cv.WaitWithTimeout(&logged_mutex, absl::Milliseconds(20));
- logged_mutex.Unlock();
- logged_mutex.ReaderLock();
- logged_mutex.ReaderUnlock();
- logged_mutex.Lock();
- logged_mutex.Unlock();
- logged_cv.Signal();
- logged_cv.SignalAll();
- }
- // --------------------------------------------------------
- // Generate the vector of thread counts for tests parameterized on thread count.
- static std::vector<int> AllThreadCountValues() {
- if (kExtendedTest) {
- return {2, 4, 8, 10, 16, 20, 24, 30, 32};
- }
- return {2, 4, 10};
- }
- // A test fixture parameterized by thread count.
- class MutexVariableThreadCountTest : public ::testing::TestWithParam<int> {};
- // Instantiate the above with AllThreadCountOptions().
- INSTANTIATE_TEST_SUITE_P(ThreadCounts, MutexVariableThreadCountTest,
- ::testing::ValuesIn(AllThreadCountValues()),
- ::testing::PrintToStringParamName());
- // Reduces iterations by some factor for slow platforms
- // (determined empirically).
- static int ScaleIterations(int x) {
- // ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE is set in the implementation
- // of Mutex that uses either std::mutex or pthread_mutex_t. Use
- // these as keys to determine the slow implementation.
- #if defined(ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE)
- return x / 10;
- #else
- return x;
- #endif
- }
- TEST_P(MutexVariableThreadCountTest, Mutex) {
- int threads = GetParam();
- int iterations = ScaleIterations(10000000) / threads;
- int operations = threads * iterations;
- EXPECT_EQ(RunTest(&TestMu, threads, iterations, operations), operations);
- #if !defined(ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED)
- iterations = std::min(iterations, 10);
- operations = threads * iterations;
- EXPECT_EQ(RunTestWithInvariantDebugging(&TestMu, threads, iterations,
- operations, CheckSumG0G1),
- operations);
- #endif
- }
- TEST_P(MutexVariableThreadCountTest, Try) {
- int threads = GetParam();
- int iterations = 1000000 / threads;
- int operations = iterations * threads;
- EXPECT_EQ(RunTest(&TestTry, threads, iterations, operations), operations);
- #if !defined(ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED)
- iterations = std::min(iterations, 10);
- operations = threads * iterations;
- EXPECT_EQ(RunTestWithInvariantDebugging(&TestTry, threads, iterations,
- operations, CheckSumG0G1),
- operations);
- #endif
- }
- TEST_P(MutexVariableThreadCountTest, R20ms) {
- int threads = GetParam();
- int iterations = 100;
- int operations = iterations * threads;
- EXPECT_EQ(RunTest(&TestR20ms, threads, iterations, operations), 0);
- }
- TEST_P(MutexVariableThreadCountTest, RW) {
- int threads = GetParam();
- int iterations = ScaleIterations(20000000) / threads;
- int operations = iterations * threads;
- EXPECT_EQ(RunTest(&TestRW, threads, iterations, operations), operations / 2);
- #if !defined(ABSL_MUTEX_ENABLE_INVARIANT_DEBUGGING_NOT_IMPLEMENTED)
- iterations = std::min(iterations, 10);
- operations = threads * iterations;
- EXPECT_EQ(RunTestWithInvariantDebugging(&TestRW, threads, iterations,
- operations, CheckSumG0G1),
- operations / 2);
- #endif
- }
- TEST_P(MutexVariableThreadCountTest, Await) {
- int threads = GetParam();
- int iterations = ScaleIterations(500000);
- int operations = iterations;
- EXPECT_EQ(RunTest(&TestAwait, threads, iterations, operations), operations);
- }
- TEST_P(MutexVariableThreadCountTest, SignalAll) {
- int threads = GetParam();
- int iterations = 200000 / threads;
- int operations = iterations;
- EXPECT_EQ(RunTest(&TestSignalAll, threads, iterations, operations),
- operations);
- }
- TEST(Mutex, Signal) {
- int threads = 2; // TestSignal must use two threads
- int iterations = 200000;
- int operations = iterations;
- EXPECT_EQ(RunTest(&TestSignal, threads, iterations, operations), operations);
- }
- TEST(Mutex, Timed) {
- int threads = 10; // Use a fixed thread count of 10
- int iterations = 1000;
- int operations = iterations;
- EXPECT_EQ(RunTest(&TestCVTimeout, threads, iterations, operations),
- operations);
- }
- TEST(Mutex, CVTime) {
- int threads = 10; // Use a fixed thread count of 10
- int iterations = 1;
- EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1),
- threads * iterations);
- }
- TEST(Mutex, MuTime) {
- int threads = 10; // Use a fixed thread count of 10
- int iterations = 1;
- EXPECT_EQ(RunTest(&TestMuTime, threads, iterations, 1), threads * iterations);
- }
- } // namespace
|