summaryrefslogtreecommitdiff
path: root/absl/synchronization/mutex_test.cc
diff options
context:
space:
mode:
authorGravatar Abseil Team <absl-team@google.com>2018-12-04 11:01:12 -0800
committerGravatar Ashley Hedberg <ahedberg@google.com>2018-12-04 16:54:40 -0500
commitfcb104594b0bb4b8ac306cb2f55ecdad40974683 (patch)
treed2d79d246c6a894ca6716f47c15ebb7b8796b36a /absl/synchronization/mutex_test.cc
parent6c7de165d1c82684359ccb630bb5f83263fa5ebc (diff)
Creation of LTS branch "lts_2018_12_18"20181200
- 44b0fafc62d9b8f192e8180cbe9c4b806b339d57 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 926bfeb9fff223429c12224b7514243886323e8d Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 13327debebc5c2d1d4991b69fe50450e340e50e4 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 3088e76c597e068479e82508b1770a7ad0c806b6 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - f6ae816808cd913e0e2b3e2af14f328fa1071af0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - a06c4a1d9093137b7217a5aaba8920d62e835dc0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 7b46e1d31a6b08b1c6da2a13e7b151a20446fa07 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 070f6e47b33a2909d039e620c873204f78809492 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 7990fd459e9339467814ddb95000c87cb1e4d945 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - f95179062eb65ce40895cc76f1398cce25394369 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - cc8dcd307b76a575d2e3e0958a4fe4c7193c2f68 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - a705aa78dc76fc5c79d501e61dcc077eca68a8a4 Merge pull request #194 from Mizux/windows by Xiaoyi Zhang <zhangxy988@gmail.com> - a4c3ffff11eec0ee45742f915c255e9f870b7e0f Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 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 <absl-team@google.com> - 94c298e2a0ae409e283cab96c954a685bd865a70 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 0884a6a04e4497d11b1b398cc0e422b118bf977a Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - c16d5557cd05119b5b7b1318ef778ebe3195b4a1 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 45221ccc4ed643e4209b0cc5798e97203f108fa8 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 2019e17a520575ab365b2b5134d71068182c70b8 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 5b70a8910b2e6fb0ce5193a41873139a126d2f7f Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - a00bdd176d66ef0b417d9576052a19091fbdf891 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - f340f773edab951656b19b6f1a77c964a78ec4c2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 445998d7ac4e5d3c50411d377e3b50e960d2d6c2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - e821380d69a549dc64900693942789d21aa4df5e Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - f21d187b80e3b7f08fb279775ea9c8b48c636030 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 5441bbe1db5d0f2ca24b5b60166367b0966790af Fix code snippet in comment (#174) by Loo Rong Jie <loorongjie@gmail.com> - 5aae0cffae8ffaacab965756169b34e511b353df Fix CMake build (#173) by Stephan Dollberg <stephan.dollberg@gmail.com> - 48cd2c3f351ff188bc85684b84a91b6e6d17d896 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - e291c279e458761e77a69b09b129d3d1e81f1e80 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - e01d95528ea2137a4a27a88d1f57c6cb260aafed Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 8ff1374008259719b54a8cb128ef951c02da164c Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 02451914b9ad5320f81f56a89f3eef1f8683227c Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 921fd5cf02ec0d665439a790148d59faa7d4a72c Merge pull request #166 from rongjiecomputer/cmake-test by Gennadiy Civil <gennadiycivil@users.noreply.github.com> - fb462224c058487763f263b7995d70efd0242c17 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - c075ad321696fa5072e097f0a51e4fe76a6fe13e Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 0f4bc966754ec6cd28d5f03467d56f1efdc598e3 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 6c7e5ffc43decd92f7bdfc510ad8a245a20b6dea Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - d6df769173bf0263489f98874b93034db0e479a2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 28080f5f050c9530aa9f2b39c60d8217038d64ff Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 9c987f429bba32fb4446280fd3b91e2472d71d4d Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 5e7d459eeca7bc53deab0ee9634601386b53d7c0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - bed5bd6e185c7e0311f3a1f2dab4c96083dac636 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - fefc83638fb69395d259ed245699310610429064 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - d8cfe9f2a77fbee02c09642491e62a3f3677e0f6 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - ad5c960b2eb914881d1ceba0e996a0a8f3f6ca59 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 86f0fe93ad9d6d033a319476736a3256369c1f75 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - f0f15c2778b0e4959244dd25e63f445a455870f5 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 29ff6d4860070bf8fcbd39c8805d0c32d56628a3 Removed "warning treated as error" flag from MSVC (#153) by vocaviking <vocaviking@users.noreply.github.com> - 083d04dd4a62ebbf037079b06e49b323c5e1192a Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - bea85b52733022294eef108a2e42d77b616ddca2 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 8f96be6ca60d967bd4b37f93d0a03bcff4145200 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 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 <absl-team@google.com> - 9acad869d21731f5bc50430a33fe61cc0ffcbb0b Merge pull request #150 from OlafvdSpek/patch-2 by Jonathan Cohen <cohenjon@google.com> - c2e00d341913bf03b4597ade5b056042e23e8c58 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 9e060686d1c325f34f9806b45fe77bafeed00aee Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 7aa411ceafc1272a28579cca739a97a2fb79055a Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 2c5af55ed34850d8b7dd46177c8ca53fdfda920e Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 44aa275286baf97fc13529aca547a88b180beb08 Merge pull request #143 from rongjiecomputer/kernel by Xiaoyi Zhang <zhangxy988@gmail.com> - 42f22a28401c952f1fc5942231c7fdac80811bf5 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - b973bc53ef366f0253b85eeed9a79b241884a843 Merge pull request #139 from siepkes/smartos-support by ahedberg <ahedberg@google.com> - e0def7473e52336f58759e11db4cd9467e5e0356 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - f826f1d489b61b64df1d94afbe5981841a82e5fa Merge pull request #138 from edbaunton/remove-deprecated-... by ahedberg <ahedberg@google.com> - 7b50a4a94b0c7df68b3a854c850b551aaef0a8b4 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - a5030ca5125b9d557ecfeea8acc8b1a8e49f6d27 Merge pull request #144 from rongjiecomputer/winsock2 by Xiaoyi Zhang <zhangxy988@gmail.com> - 02687955b7ca8fc02ada9b14bc247deeb108d341 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 8f612ebb152fb7e05643a2bcf78cb89a8c0641ad Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 134496a31d8b324f762de3bee9a002658c984456 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - ba8d6cf07766263723e86736f20a51c1c9c67b19 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - be1e84b988fceabcea4fc9e93f899539f0c81901 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 16ac2ec2e38cdf47f9330a312e319d57da659c10 Merge pull request #134 from rongjiecomputer/cmake by Alex Strelnikov <strel@google.com> - 7efd8dc0f1075356e9c7caa950afd1ecf854e8b9 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 87a4c07856e7dc69958019d47b2f02ae47746ec0 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> - 4491d606df34c44efda47b6d17b605262f17e182 Export of internal Abseil changes. by Abseil Team <absl-team@google.com> GitOrigin-RevId: 44b0fafc62d9b8f192e8180cbe9c4b806b339d57 Change-Id: I2c427b5b41b2d34101922048b00f3d9dafcb498d
Diffstat (limited to 'absl/synchronization/mutex_test.cc')
-rw-r--r--absl/synchronization/mutex_test.cc575
1 files changed, 348 insertions, 227 deletions
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 <vector>
#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<void()> &func,
- absl::Duration after) {
+ absl::Duration after,
+ const std::function<void()> &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 &param) {
+ return os << "from: " << param.from_file << ":" << param.from_line
+ << " use_absolute_deadline: "
+ << (param.use_absolute_deadline ? "true" : "false")
+ << " wait_timeout: " << param.wait_timeout
+ << " satisfy_condition_delay: " << param.satisfy_condition_delay
+ << " expected_result: "
+ << (param.expected_result ? "true" : "false")
+ << " expected_delay: " << param.expected_delay;
+}
+
+std::string FormatString(const TimeoutTestParam &param) {
+ std::ostringstream os;
+ os << param;
+ return os.str();
+}
+
+// Like `thread::Executor::ScheduleAt` except:
+// a) Delays zero or negative are executed immediately in the current thread.
+// b) Infinite delays are never scheduled.
+// c) Calls this test's `ScheduleAt` helper instead of using `pool` directly.
+static void RunAfterDelay(absl::Duration delay,
+ absl::synchronization_internal::ThreadPool *pool,
+ const std::function<void()> &callback) {
+ if (delay <= absl::ZeroDuration()) {
+ callback(); // immediate
+ } else if (delay != absl::InfiniteDuration()) {
+ ScheduleAfter(pool, delay, callback);
}
+}
- 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<TimeoutTestParam> {};
- void Await() {
- absl::MutexLock lock(&mu_);
- mu_.Await(c_);
- }
+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();
- 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<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
+ });
}
-
- 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<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;
}
- 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<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();
-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<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;
+ }
+ }
}
-// 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<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
- // 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;
+ }
}
}