diff options
author | Alex Li <alex.li@mongodb.com> | 2021-06-23 14:12:20 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-06-23 14:46:51 +0000 |
commit | db5c817ceaeef2aadf86832f7878c50d32cbd382 (patch) | |
tree | d333958ad8fc8a54513e41423a8888b87a5558da /src | |
parent | 8e18be404a23c90055e42658550b5b366e1422f5 (diff) | |
download | mongo-db5c817ceaeef2aadf86832f7878c50d32cbd382.tar.gz |
SERVER-55760 Fix data races in Cancelation tests for AsyncTry
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/util/future_util.h | 4 | ||||
-rw-r--r-- | src/mongo/util/future_util_test.cpp | 74 |
2 files changed, 69 insertions, 9 deletions
diff --git a/src/mongo/util/future_util.h b/src/mongo/util/future_util.h index 0d5e45870ec..9fdc53625e6 100644 --- a/src/mongo/util/future_util.h +++ b/src/mongo/util/future_util.h @@ -174,8 +174,12 @@ private: executor->sleepFor(delay.getNext(), cancelToken) .getAsync([this, self, resultPromise = std::move(resultPromise)]( Status s) mutable { + // Prevent another loop iteration when cancellation happens + // after loop body if (s.isOK()) { runImpl(std::move(resultPromise)); + } else { + resultPromise.setError(std::move(s)); } }); } diff --git a/src/mongo/util/future_util_test.cpp b/src/mongo/util/future_util_test.cpp index d19d8044e4e..3377ea210d2 100644 --- a/src/mongo/util/future_util_test.cpp +++ b/src/mongo/util/future_util_test.cpp @@ -423,27 +423,83 @@ TEST_F(AsyncTryUntilTest, AsyncTryUntilCanBeCanceled) { ASSERT_EQ(resultFut.getNoThrow(), kCanceledStatus); } -TEST_F(AsyncTryUntilTest, AsyncTryUntilWithDelayCanBeCanceled) { +TEST_F(AsyncTryUntilTest, AsyncTryUntilWithDelayCanBeCanceledWhileLoopBodyIsExecuting) { CancellationSource cancelSource; - auto resultFut = AsyncTry([] {}) + unittest::Barrier barrierBeforeCancel{2}, barrierAfterCancel{2}; + int timesRanCallback = 0; + // Arbitrary delay used, enforce only one loop body execution with timesRanCallback + auto resultFut = AsyncTry([&] { + timesRanCallback += 1; + barrierBeforeCancel.countDownAndWait(); + barrierAfterCancel.countDownAndWait(); + }) .until([](Status) { return false; }) - .withDelayBetweenIterations(Hours(1000)) + .withDelayBetweenIterations(Milliseconds(10)) + .on(executor(), cancelSource.token()); + // Enforce cancellation during loop body execution + barrierBeforeCancel.countDownAndWait(); + cancelSource.cancel(); + barrierAfterCancel.countDownAndWait(); + ASSERT_EQ(resultFut.getNoThrow(), kCanceledStatus); + ASSERT_EQ(timesRanCallback, 1); +} + +TEST_F(AsyncTryUntilTest, AsyncTryUntilWithDelayCanBeCanceledAfterLoopBodyIsDoneExecuting) { + CancellationSource cancelSource; + unittest::Barrier barrierBeforeCancel{2}, barrierAfterCancel{2}; + int timesRanCallback = 0; + auto resultFut = AsyncTry([&] { timesRanCallback += 1; }) + .until([&](Status) { + barrierBeforeCancel.countDownAndWait(); + barrierAfterCancel.countDownAndWait(); + return false; + }) + .withDelayBetweenIterations(Milliseconds(10)) .on(executor(), cancelSource.token()); - // Since the "until" condition is false, and the delay between iterations is very long, the only - // way this test should pass without hanging is if the future produced by TaskExecutor::sleepFor - // is resolved and set with ErrorCodes::CallbackCanceled well _before_ the deadline. + // Enforce cancellation after loop body executes once + barrierBeforeCancel.countDownAndWait(); cancelSource.cancel(); + barrierAfterCancel.countDownAndWait(); ASSERT_EQ(resultFut.getNoThrow(), kCanceledStatus); + ASSERT_EQ(timesRanCallback, 1); } -TEST_F(AsyncTryUntilTest, AsyncTryUntilWithBackoffCanBeCanceled) { +TEST_F(AsyncTryUntilTest, AsyncTryUntilWithBackoffCanBeCanceledWhileLoopBodyIsExecuting) { CancellationSource cancelSource; - auto resultFut = AsyncTry([] {}) + unittest::Barrier barrierBeforeCancel{2}, barrierAfterCancel{2}; + int timesRanCallback = 0; + auto resultFut = AsyncTry([&] { + timesRanCallback += 1; + barrierBeforeCancel.countDownAndWait(); + barrierAfterCancel.countDownAndWait(); + }) .until([](Status) { return false; }) - .withBackoffBetweenIterations(TestBackoff{Seconds(10000000)}) + .withBackoffBetweenIterations(TestBackoff{Milliseconds(10)}) + .on(executor(), cancelSource.token()); + barrierBeforeCancel.countDownAndWait(); + cancelSource.cancel(); + barrierAfterCancel.countDownAndWait(); + ASSERT_EQ(resultFut.getNoThrow(), kCanceledStatus); + ASSERT_EQ(timesRanCallback, 1); +} + +TEST_F(AsyncTryUntilTest, AsyncTryUntilWithBackoffCanBeCanceledAfterLoopBodyIsDoneExecuting) { + CancellationSource cancelSource; + unittest::Barrier barrierBeforeCancel{2}, barrierAfterCancel{2}; + int timesRanCallback = 0; + auto resultFut = AsyncTry([&] { timesRanCallback += 1; }) + .until([&](Status) { + barrierBeforeCancel.countDownAndWait(); + barrierAfterCancel.countDownAndWait(); + return false; + }) + .withBackoffBetweenIterations(TestBackoff{Milliseconds(10)}) .on(executor(), cancelSource.token()); + barrierBeforeCancel.countDownAndWait(); cancelSource.cancel(); + barrierAfterCancel.countDownAndWait(); ASSERT_EQ(resultFut.getNoThrow(), kCanceledStatus); + ASSERT_EQ(timesRanCallback, 1); } TEST_F(AsyncTryUntilTest, CanceledTryUntilLoopDoesNotExecuteIfAlreadyCanceled) { |