From fcb104594b0bb4b8ac306cb2f55ecdad40974683 Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Tue, 4 Dec 2018 11:01:12 -0800 Subject: Creation of LTS branch "lts_2018_12_18" - 44b0fafc62d9b8f192e8180cbe9c4b806b339d57 Export of internal Abseil changes. by Abseil Team - 926bfeb9fff223429c12224b7514243886323e8d Export of internal Abseil changes. by Abseil Team - 13327debebc5c2d1d4991b69fe50450e340e50e4 Export of internal Abseil changes. by Abseil Team - 3088e76c597e068479e82508b1770a7ad0c806b6 Export of internal Abseil changes. by Abseil Team - f6ae816808cd913e0e2b3e2af14f328fa1071af0 Export of internal Abseil changes. by Abseil Team - a06c4a1d9093137b7217a5aaba8920d62e835dc0 Export of internal Abseil changes. by Abseil Team - 7b46e1d31a6b08b1c6da2a13e7b151a20446fa07 Export of internal Abseil changes. by Abseil Team - 070f6e47b33a2909d039e620c873204f78809492 Export of internal Abseil changes. by Abseil Team - 7990fd459e9339467814ddb95000c87cb1e4d945 Export of internal Abseil changes. by Abseil Team - f95179062eb65ce40895cc76f1398cce25394369 Export of internal Abseil changes. by Abseil Team - cc8dcd307b76a575d2e3e0958a4fe4c7193c2f68 Export of internal Abseil changes. by Abseil Team - a705aa78dc76fc5c79d501e61dcc077eca68a8a4 Merge pull request #194 from Mizux/windows by Xiaoyi Zhang - a4c3ffff11eec0ee45742f915c255e9f870b7e0f Export of internal Abseil changes. by Abseil Team - 01174578651b73021d9b8c3820f6fea707dacdf0 Merge pull request #201 from ccawley2011/fix-byteswap by Matt Calabrese <38107210+mattcalabrese-google@users.noreply.github.com> - f86f9413856b65afdd61fea938d684b8ab73115a Export of internal Abseil changes. by Abseil Team - 94c298e2a0ae409e283cab96c954a685bd865a70 Export of internal Abseil changes. by Abseil Team - 0884a6a04e4497d11b1b398cc0e422b118bf977a Export of internal Abseil changes. by Abseil Team - c16d5557cd05119b5b7b1318ef778ebe3195b4a1 Export of internal Abseil changes. by Abseil Team - 45221ccc4ed643e4209b0cc5798e97203f108fa8 Export of internal Abseil changes. by Abseil Team - 2019e17a520575ab365b2b5134d71068182c70b8 Export of internal Abseil changes. by Abseil Team - 5b70a8910b2e6fb0ce5193a41873139a126d2f7f Export of internal Abseil changes. by Abseil Team - a00bdd176d66ef0b417d9576052a19091fbdf891 Export of internal Abseil changes. by Abseil Team - f340f773edab951656b19b6f1a77c964a78ec4c2 Export of internal Abseil changes. by Abseil Team - 445998d7ac4e5d3c50411d377e3b50e960d2d6c2 Export of internal Abseil changes. by Abseil Team - e821380d69a549dc64900693942789d21aa4df5e Export of internal Abseil changes. by Abseil Team - f21d187b80e3b7f08fb279775ea9c8b48c636030 Export of internal Abseil changes. by Abseil Team - 5441bbe1db5d0f2ca24b5b60166367b0966790af Fix code snippet in comment (#174) by Loo Rong Jie - 5aae0cffae8ffaacab965756169b34e511b353df Fix CMake build (#173) by Stephan Dollberg - 48cd2c3f351ff188bc85684b84a91b6e6d17d896 Export of internal Abseil changes. by Abseil Team - e291c279e458761e77a69b09b129d3d1e81f1e80 Export of internal Abseil changes. by Abseil Team - e01d95528ea2137a4a27a88d1f57c6cb260aafed Export of internal Abseil changes. by Abseil Team - 8ff1374008259719b54a8cb128ef951c02da164c Export of internal Abseil changes. by Abseil Team - 02451914b9ad5320f81f56a89f3eef1f8683227c Export of internal Abseil changes. by Abseil Team - 921fd5cf02ec0d665439a790148d59faa7d4a72c Merge pull request #166 from rongjiecomputer/cmake-test by Gennadiy Civil - fb462224c058487763f263b7995d70efd0242c17 Export of internal Abseil changes. by Abseil Team - c075ad321696fa5072e097f0a51e4fe76a6fe13e Export of internal Abseil changes. by Abseil Team - 0f4bc966754ec6cd28d5f03467d56f1efdc598e3 Export of internal Abseil changes. by Abseil Team - 6c7e5ffc43decd92f7bdfc510ad8a245a20b6dea Export of internal Abseil changes. by Abseil Team - d6df769173bf0263489f98874b93034db0e479a2 Export of internal Abseil changes. by Abseil Team - 28080f5f050c9530aa9f2b39c60d8217038d64ff Export of internal Abseil changes. by Abseil Team - 9c987f429bba32fb4446280fd3b91e2472d71d4d Export of internal Abseil changes. by Abseil Team - 5e7d459eeca7bc53deab0ee9634601386b53d7c0 Export of internal Abseil changes. by Abseil Team - bed5bd6e185c7e0311f3a1f2dab4c96083dac636 Export of internal Abseil changes. by Abseil Team - fefc83638fb69395d259ed245699310610429064 Export of internal Abseil changes. by Abseil Team - d8cfe9f2a77fbee02c09642491e62a3f3677e0f6 Export of internal Abseil changes. by Abseil Team - ad5c960b2eb914881d1ceba0e996a0a8f3f6ca59 Export of internal Abseil changes. by Abseil Team - 86f0fe93ad9d6d033a319476736a3256369c1f75 Export of internal Abseil changes. by Abseil Team - f0f15c2778b0e4959244dd25e63f445a455870f5 Export of internal Abseil changes. by Abseil Team - 29ff6d4860070bf8fcbd39c8805d0c32d56628a3 Removed "warning treated as error" flag from MSVC (#153) by vocaviking - 083d04dd4a62ebbf037079b06e49b323c5e1192a Export of internal Abseil changes. by Abseil Team - bea85b52733022294eef108a2e42d77b616ddca2 Export of internal Abseil changes. by Abseil Team - 8f96be6ca60d967bd4b37f93d0a03bcff4145200 Export of internal Abseil changes. by Abseil Team - 92e07e5590752d6b8e67f7f2f86c6286561e8cea Merge pull request #152 from clnperez/fix-multi-defines-p... by Derek Mauro <761129+derekmauro@users.noreply.github.com> - 2125e6444a9de9e41f21ecdc674dd7d8759c149d Export of internal Abseil changes. by Abseil Team - 9acad869d21731f5bc50430a33fe61cc0ffcbb0b Merge pull request #150 from OlafvdSpek/patch-2 by Jonathan Cohen - c2e00d341913bf03b4597ade5b056042e23e8c58 Export of internal Abseil changes. by Abseil Team - 9e060686d1c325f34f9806b45fe77bafeed00aee Export of internal Abseil changes. by Abseil Team - 7aa411ceafc1272a28579cca739a97a2fb79055a Export of internal Abseil changes. by Abseil Team - 2c5af55ed34850d8b7dd46177c8ca53fdfda920e Export of internal Abseil changes. by Abseil Team - 44aa275286baf97fc13529aca547a88b180beb08 Merge pull request #143 from rongjiecomputer/kernel by Xiaoyi Zhang - 42f22a28401c952f1fc5942231c7fdac80811bf5 Export of internal Abseil changes. by Abseil Team - b973bc53ef366f0253b85eeed9a79b241884a843 Merge pull request #139 from siepkes/smartos-support by ahedberg - e0def7473e52336f58759e11db4cd9467e5e0356 Export of internal Abseil changes. by Abseil Team - f826f1d489b61b64df1d94afbe5981841a82e5fa Merge pull request #138 from edbaunton/remove-deprecated-... by ahedberg - 7b50a4a94b0c7df68b3a854c850b551aaef0a8b4 Export of internal Abseil changes. by Abseil Team - a5030ca5125b9d557ecfeea8acc8b1a8e49f6d27 Merge pull request #144 from rongjiecomputer/winsock2 by Xiaoyi Zhang - 02687955b7ca8fc02ada9b14bc247deeb108d341 Export of internal Abseil changes. by Abseil Team - 8f612ebb152fb7e05643a2bcf78cb89a8c0641ad Export of internal Abseil changes. by Abseil Team - 134496a31d8b324f762de3bee9a002658c984456 Export of internal Abseil changes. by Abseil Team - ba8d6cf07766263723e86736f20a51c1c9c67b19 Export of internal Abseil changes. by Abseil Team - be1e84b988fceabcea4fc9e93f899539f0c81901 Export of internal Abseil changes. by Abseil Team - 16ac2ec2e38cdf47f9330a312e319d57da659c10 Merge pull request #134 from rongjiecomputer/cmake by Alex Strelnikov - 7efd8dc0f1075356e9c7caa950afd1ecf854e8b9 Export of internal Abseil changes. by Abseil Team - 87a4c07856e7dc69958019d47b2f02ae47746ec0 Export of internal Abseil changes. by Abseil Team - 4491d606df34c44efda47b6d17b605262f17e182 Export of internal Abseil changes. by Abseil Team GitOrigin-RevId: 44b0fafc62d9b8f192e8180cbe9c4b806b339d57 Change-Id: I2c427b5b41b2d34101922048b00f3d9dafcb498d --- absl/synchronization/mutex_test.cc | 575 ++++++++++++++++++++++--------------- 1 file changed, 348 insertions(+), 227 deletions(-) (limited to 'absl/synchronization/mutex_test.cc') diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index 53b93784..b2820e20 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc @@ -29,6 +29,7 @@ #include #include "gtest/gtest.h" +#include "absl/base/attributes.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/sysinfo.h" #include "absl/memory/memory.h" @@ -54,8 +55,8 @@ CreateDefaultPool() { // 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, - const std::function &func, - absl::Duration after) { + absl::Duration after, + const std::function &func) { tp->Schedule([func, after] { absl::SleepFor(after); func(); @@ -1150,249 +1151,369 @@ TEST(Mutex, DeadlockIdBug) NO_THREAD_SAFETY_ANALYSIS { // and so never expires/passes, and one that will expire/pass in the near // future. -// Encapsulate a Mutex-protected bool with its associated Condition/CondVar. -class Cond { - public: - explicit Cond(bool use_deadline) : use_deadline_(use_deadline), c_(&b_) {} - - void Set(bool v) { - absl::MutexLock lock(&mu_); - b_ = v; +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; } - - bool AwaitWithTimeout(absl::Duration timeout) { - absl::MutexLock lock(&mu_); - return use_deadline_ ? mu_.AwaitWithDeadline(c_, absl::Now() + timeout) - : mu_.AwaitWithTimeout(c_, timeout); + // 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; +}; - bool LockWhenWithTimeout(absl::Duration timeout) { - bool b = use_deadline_ ? mu_.LockWhenWithDeadline(c_, absl::Now() + timeout) - : mu_.LockWhenWithTimeout(c_, timeout); - mu_.Unlock(); - return b; +// 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 &callback) { + if (delay <= absl::ZeroDuration()) { + callback(); // immediate + } else if (delay != absl::InfiniteDuration()) { + ScheduleAfter(pool, delay, callback); } +} - bool ReaderLockWhenWithTimeout(absl::Duration timeout) { - bool b = use_deadline_ - ? mu_.ReaderLockWhenWithDeadline(c_, absl::Now() + timeout) - : mu_.ReaderLockWhenWithTimeout(c_, timeout); - mu_.ReaderUnlock(); - return b; - } +class TimeoutTest : public ::testing::Test, + public ::testing::WithParamInterface {}; - void Await() { - absl::MutexLock lock(&mu_); - mu_.Await(c_); - } +std::vector 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(); - void Signal(bool v) { - absl::MutexLock lock(&mu_); - b_ = v; - cv_.Signal(); + // Every test case is run twice; once using the absolute deadline API and once + // using the relative timeout API. + std::vector 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 + }); } - - bool WaitWithTimeout(absl::Duration timeout) { - absl::MutexLock lock(&mu_); - absl::Time deadline = absl::Now() + timeout; - if (use_deadline_) { - while (!b_ && !cv_.WaitWithDeadline(&mu_, deadline)) { - } - } else { - while (!b_ && !cv_.WaitWithTimeout(&mu_, timeout)) { - timeout = deadline - absl::Now(); // recompute timeout - } + return values; +} + +// Instantiate `TimeoutTest` with `MakeTimeoutTestParamValues()`. +INSTANTIATE_TEST_CASE_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 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; } - return b_; - } - - void Wait() { - absl::MutexLock lock(&mu_); - while (!b_) cv_.Wait(&mu_); - } - - private: - const bool use_deadline_; - - bool b_; - absl::Condition c_; - absl::CondVar cv_; - absl::Mutex mu_; -}; - -class OperationTimer { - public: - OperationTimer() : start_(absl::Now()) {} - absl::Duration Get() const { return absl::Now() - start_; } - - private: - const absl::Time start_; -}; - -static void CheckResults(bool exp_result, bool act_result, - absl::Duration exp_duration, - absl::Duration act_duration) { - ABSL_RAW_CHECK(exp_result == act_result, "CheckResults failed"); - // Allow for some worse-case scheduling delay and clock skew. - if ((exp_duration - absl::Milliseconds(40) > act_duration) || - (exp_duration + absl::Milliseconds(150) < act_duration)) { - ABSL_RAW_LOG(FATAL, "CheckResults failed: operation took %s, expected %s", - absl::FormatDuration(act_duration).c_str(), - absl::FormatDuration(exp_duration).c_str()); } } -static void TestAwaitTimeout(Cond *cp, absl::Duration timeout, bool exp_result, - absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->AwaitWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); -} - -static void TestLockWhenTimeout(Cond *cp, absl::Duration timeout, - bool exp_result, absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->LockWhenWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); -} +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 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(); -static void TestReaderLockWhenTimeout(Cond *cp, absl::Duration timeout, - bool exp_result, - absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->ReaderLockWhenWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); + if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { + EXPECT_EQ(params.expected_result, result); + break; + } + } } -static void TestWaitTimeout(Cond *cp, absl::Duration timeout, bool exp_result, - absl::Duration exp_duration) { - OperationTimer t; - bool act_result = cp->WaitWithTimeout(timeout); - CheckResults(exp_result, act_result, exp_duration, t.Get()); +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 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; + } + } } -// Tests with a negative timeout (deadline in the past), which should -// immediately return the current state of the condition. -static void TestNegativeTimeouts(absl::synchronization_internal::ThreadPool *tp, - Cond *cp) { - const absl::Duration negative = -absl::InfiniteDuration(); - const absl::Duration immediate = absl::ZeroDuration(); - - // The condition is already true: - cp->Set(true); - TestAwaitTimeout(cp, negative, true, immediate); - TestLockWhenTimeout(cp, negative, true, immediate); - TestReaderLockWhenTimeout(cp, negative, true, immediate); - TestWaitTimeout(cp, negative, true, immediate); - - // The condition becomes true, but the timeout has already expired: - const absl::Duration delay = absl::Milliseconds(200); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), 3 * delay); - TestAwaitTimeout(cp, negative, false, immediate); - TestLockWhenTimeout(cp, negative, false, immediate); - TestReaderLockWhenTimeout(cp, negative, false, immediate); - cp->Await(); // wait for the scheduled Set() to complete - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay); - TestWaitTimeout(cp, negative, false, immediate); - cp->Wait(); // wait for the scheduled Signal() to complete - - // The condition never becomes true: - cp->Set(false); - TestAwaitTimeout(cp, negative, false, immediate); - TestLockWhenTimeout(cp, negative, false, immediate); - TestReaderLockWhenTimeout(cp, negative, false, immediate); - TestWaitTimeout(cp, negative, false, immediate); -} - -// Tests with an infinite timeout (deadline in the infinite future), which -// should only return when the condition becomes true. -static void TestInfiniteTimeouts(absl::synchronization_internal::ThreadPool *tp, - Cond *cp) { - const absl::Duration infinite = absl::InfiniteDuration(); - const absl::Duration immediate = absl::ZeroDuration(); - - // The condition is already true: - cp->Set(true); - TestAwaitTimeout(cp, infinite, true, immediate); - TestLockWhenTimeout(cp, infinite, true, immediate); - TestReaderLockWhenTimeout(cp, infinite, true, immediate); - TestWaitTimeout(cp, infinite, true, immediate); - - // The condition becomes true before the (infinite) expiry: - const absl::Duration delay = absl::Milliseconds(200); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay); - TestAwaitTimeout(cp, infinite, true, delay); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay); - TestLockWhenTimeout(cp, infinite, true, delay); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay); - TestReaderLockWhenTimeout(cp, infinite, true, delay); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay); - TestWaitTimeout(cp, infinite, true, delay); -} - -// Tests with a (small) finite timeout (deadline soon), with the condition -// becoming true both before and after its expiry. -static void TestFiniteTimeouts(absl::synchronization_internal::ThreadPool *tp, - Cond *cp) { - const absl::Duration finite = absl::Milliseconds(400); - const absl::Duration immediate = absl::ZeroDuration(); +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 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 - // The condition is already true: - cp->Set(true); - TestAwaitTimeout(cp, finite, true, immediate); - TestLockWhenTimeout(cp, finite, true, immediate); - TestReaderLockWhenTimeout(cp, finite, true, immediate); - TestWaitTimeout(cp, finite, true, immediate); - - // The condition becomes true before the expiry: - const absl::Duration delay1 = finite / 2; - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay1); - TestAwaitTimeout(cp, finite, true, delay1); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay1); - TestLockWhenTimeout(cp, finite, true, delay1); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), delay1); - TestReaderLockWhenTimeout(cp, finite, true, delay1); - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay1); - TestWaitTimeout(cp, finite, true, delay1); - - // The condition becomes true, but the timeout has already expired: - const absl::Duration delay2 = finite * 2; - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Set, cp, true), 3 * delay2); - TestAwaitTimeout(cp, finite, false, finite); - TestLockWhenTimeout(cp, finite, false, finite); - TestReaderLockWhenTimeout(cp, finite, false, finite); - cp->Await(); // wait for the scheduled Set() to complete - cp->Set(false); - ScheduleAfter(tp, std::bind(&Cond::Signal, cp, true), delay2); - TestWaitTimeout(cp, finite, false, finite); - cp->Wait(); // wait for the scheduled Signal() to complete - - // The condition never becomes true: - cp->Set(false); - TestAwaitTimeout(cp, finite, false, finite); - TestLockWhenTimeout(cp, finite, false, finite); - TestReaderLockWhenTimeout(cp, finite, false, finite); - TestWaitTimeout(cp, finite, false, finite); -} - -TEST(Mutex, Timeouts) { - auto tp = CreateDefaultPool(); - for (bool use_deadline : {false, true}) { - Cond cond(use_deadline); - TestNegativeTimeouts(tp.get(), &cond); - TestInfiniteTimeouts(tp.get(), &cond); - TestFiniteTimeouts(tp.get(), &cond); + if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { + EXPECT_EQ(params.expected_result, result); + break; + } } } -- cgit v1.2.3