diff options
| author | Matt Diener <matt.diener@mongodb.com> | 2022-09-06 16:55:28 +0000 |
|---|---|---|
| committer | MongoDB Bot <mongo-bot@mongodb.com> | 2024-02-13 19:38:15 +0000 |
| commit | 17c0d8bbee46f15f1574079e266a9997cebe6d0e (patch) | |
| tree | edae1dcbe1e46d4f5c91dd4c40c8d1d5fb0e322b /src | |
| parent | 5bae18f9e299f3d02e34f93d05ba34a40871e29e (diff) | |
SERVER-66036 add correct Future validity semanticsr5.0.25-rc0r5.0.25
(cherry picked from commit 2b958166154c99dc554591d57ce44caee35cb446)
GitOrigin-RevId: dab8c370be035063df257746cc1a2de1bf36bcd4
Diffstat (limited to 'src')
| -rw-r--r-- | src/mongo/util/future.h | 15 | ||||
| -rw-r--r-- | src/mongo/util/future_impl.h | 97 | ||||
| -rw-r--r-- | src/mongo/util/future_test_valid.cpp | 269 |
3 files changed, 316 insertions, 65 deletions
diff --git a/src/mongo/util/future.h b/src/mongo/util/future.h index e76f1fefc93..5161d075121 100644 --- a/src/mongo/util/future.h +++ b/src/mongo/util/future.h @@ -183,8 +183,7 @@ public: /** * Returns whether this SemiFuture can or will be able to access a deferred status or value. * - * NOTE: valid() will still return true if the value inside of the future is moved from. This - * should not be used as a way to determine usage validity until SERVER-66036. + * TODO(SERVER-66010): Document validity semantics in `Future` member functions. */ bool valid() const { return _impl.valid(); @@ -223,7 +222,6 @@ public: T get(Interruptible* interruptible = Interruptible::notInterruptible()) && { return std::move(_impl).get(interruptible); } - future_details::AddRefUnlessVoid<T> get( Interruptible* interruptible = Interruptible::notInterruptible()) & { return _impl.get(interruptible); @@ -1277,7 +1275,7 @@ MONGO_COMPILER_NOINLINE auto ExecutorFuture<T>::wrapCBHelper(unique_function<Sig } template <typename T> - inline ExecutorFuture<T> SemiFuture<T>::thenRunOn(ExecutorPtr exec) && noexcept { + ExecutorFuture<T> SemiFuture<T>::thenRunOn(ExecutorPtr exec) && noexcept { return ExecutorFuture<T>(std::move(exec), std::move(_impl)); } @@ -1286,13 +1284,16 @@ template <typename T> return Future<T>(std::move(_impl)); } +namespace future_details { template <typename T> - inline SharedSemiFuture<future_details::FakeVoidToVoid<T>> - future_details::FutureImpl<T>::share() && noexcept { + SharedSemiFuture<FakeVoidToVoid<T>> FutureImpl<T>::share() && noexcept { + invariant(valid()); using Out = SharedSemiFuture<FakeVoidToVoid<T>>; if (_immediate) - return Out(SharedStateHolder<FakeVoidToVoid<T>>::makeReady(std::move(*_immediate))); + return Out(SharedStateHolder<FakeVoidToVoid<T>>::makeReady(*std::exchange(_immediate, {}))); + return Out(SharedStateHolder<FakeVoidToVoid<T>>(std::move(_shared))); } +} // namespace future_details } // namespace mongo diff --git a/src/mongo/util/future_impl.h b/src/mongo/util/future_impl.h index b1ef0ffd955..f8373537984 100644 --- a/src/mongo/util/future_impl.h +++ b/src/mongo/util/future_impl.h @@ -651,6 +651,7 @@ public: } bool isReady() const { + invariant(_shared); return _shared->state.load(std::memory_order_acquire) == SSBState::kFinished; } @@ -658,11 +659,17 @@ public: return _shared != nullptr; } + void reset() { + _shared.reset(); + } + void wait(Interruptible* interruptible) const { + invariant(_shared); _shared->wait(interruptible); } Status waitNoThrow(Interruptible* interruptible) const noexcept { + invariant(_shared); try { _shared->wait(interruptible); } catch (const DBException& ex) { @@ -673,34 +680,41 @@ public: } T get(Interruptible* interruptible) && { + invariant(_shared); _shared->wait(interruptible); - uassertStatusOK(std::move(_shared->status)); - return std::move(*(_shared->data)); + auto sharedState = std::move(_shared); + uassertStatusOK(std::move(sharedState->status)); + return std::move(*sharedState->data); } T& get(Interruptible* interruptible) & { + invariant(_shared); _shared->wait(interruptible); uassertStatusOK(_shared->status); return *(_shared->data); } const T& get(Interruptible* interruptible) const& { + invariant(_shared); _shared->wait(interruptible); uassertStatusOK(_shared->status); return *(_shared->data); } StatusWith<T> getNoThrow(Interruptible* interruptible) && noexcept { + invariant(_shared); try { _shared->wait(interruptible); } catch (const DBException& ex) { return ex.toStatus(); } - - if (!_shared->status.isOK()) - return std::move(_shared->status); - return std::move(*_shared->data); + auto sharedState = std::move(_shared); + if (!sharedState->status.isOK()) { + return std::move(sharedState->status); + } + return std::move(*sharedState->data); } StatusWith<T> getNoThrow(Interruptible* interruptible) const& noexcept { + invariant(_shared); try { _shared->wait(interruptible); } catch (const DBException& ex) { @@ -717,10 +731,12 @@ public: } SharedState<T>* operator->() { + invariant(_shared); return _shared.operator->(); } SharedStateHolder<VoidToFakeVoid<T>> addChild() const { + invariant(_shared); return SharedStateHolder<VoidToFakeVoid<T>>(_shared->addChild()); } @@ -765,6 +781,10 @@ public: return _inner.valid(); } + void reset() { + _inner.reset(); + } + void wait(Interruptible* interruptible) const { _inner.wait(interruptible); } @@ -829,7 +849,7 @@ public: SharedSemiFuture<FakeVoidToVoid<T>> share() && noexcept; bool isReady() const { - return _immediate || _shared.isReady(); + return _immediate || (_shared.valid() && _shared.isReady()); } /** @@ -856,7 +876,7 @@ public: T get(Interruptible* interruptible) && { if (_immediate) - return std::move(*_immediate); + return *std::exchange(_immediate, {}); return std::move(_shared).get(interruptible); } T& get(Interruptible* interruptible) & { @@ -872,7 +892,7 @@ public: StatusWith<T> getNoThrow(Interruptible* interruptible) && noexcept { if (_immediate) - return std::move(*_immediate); + return *std::exchange(_immediate, {}); return std::move(_shared).getNoThrow(interruptible); } StatusWith<T> getNoThrow(Interruptible* interruptible) const& noexcept { @@ -1199,16 +1219,17 @@ private: template <typename SuccessFunc, typename FailFunc, typename NotReady> auto generalImpl(SuccessFunc&& success, FailFunc&& fail, NotReady&& notReady) noexcept { if (_immediate) { - return success(std::move(*_immediate)); + return success(*std::exchange(_immediate, {})); } auto oldState = _shared->state.load(std::memory_order_acquire); dassert(oldState != SSBState::kHaveCallback); if (oldState == SSBState::kFinished) { - if (_shared->status.isOK()) { - return success(std::move(*_shared->data)); + auto sharedState = std::move(_shared); + if (sharedState->status.isOK()) { + return success(std::move(*sharedState->data)); } else { - return fail(std::move(_shared->status)); + return fail(std::move(sharedState->status)); } } @@ -1216,13 +1237,16 @@ private: // support both void- and value-returning notReady implementations since we can't assign // void to a variable. ON_BLOCK_EXIT([&] { - dassert(_shared->children.empty()); + // The setting of a callback by `notReady` must explicitly make this Future non-valid(). + auto sharedState = std::move(_shared); + + dassert(sharedState->children.empty()); // oldState could be either kInit or kWaitingOrHaveChildren, depending on whether we've // failed a call to wait(). - if (MONGO_unlikely(!_shared->state.compare_exchange_strong( + if (MONGO_unlikely(!sharedState->state.compare_exchange_strong( oldState, SSBState::kHaveCallback, std::memory_order_acq_rel))) { dassert(oldState == SSBState::kFinished); - _shared->callback(_shared.getPtr()); + sharedState->callback(sharedState.getPtr()); } }); @@ -1262,7 +1286,7 @@ private: } template <typename Result, typename OnReady> - inline FutureImpl<Result> makeContinuation(OnReady&& onReady) { + FutureImpl<Result> makeContinuation(OnReady&& onReady) { invariant(!_shared->callback && !_shared->continuation); auto continuation = make_intrusive<SharedState<Result>>(); @@ -1277,8 +1301,45 @@ private: return FutureImpl<Result>(SharedStateHolder<Result>(std::move(continuation))); } + /** + * Ensures clearing of the moved-from optional in the move assignment operator and move + * constructor. Regular boost::optional doesn't enforce such strict semantics. This behaviour + * enables `has_value`, `operator!`, `operator bool` to be a source of truth after a move. + */ + class ResetOnMoveOptional : public boost::optional<T> { + using Base = boost::optional<T>; + + public: + using Base::Base; + using Base::operator=; + + ResetOnMoveOptional(ResetOnMoveOptional&& other) noexcept( + std::is_nothrow_move_assignable_v<T>&& std::is_nothrow_move_constructible_v<T>) + : Base(other._stealBase()) {} + + ResetOnMoveOptional& operator=(ResetOnMoveOptional&& other) noexcept( + std::is_nothrow_move_assignable_v<T>&& std::is_nothrow_move_constructible_v<T>) { + if (this != &other) + _base() = other._stealBase(); + return *this; + } + + private: + Base& _base() { + return *this; + } + + const Base& _base() const { + return *this; + } + + Base _stealBase() { + return std::exchange(_base(), {}); + } + }; + // At most one of these will be active. - boost::optional<T> _immediate; + ResetOnMoveOptional _immediate; SharedStateHolder<T> _shared; }; diff --git a/src/mongo/util/future_test_valid.cpp b/src/mongo/util/future_test_valid.cpp index 2ca85a19f26..80b4452f1a6 100644 --- a/src/mongo/util/future_test_valid.cpp +++ b/src/mongo/util/future_test_valid.cpp @@ -40,21 +40,14 @@ namespace mongo { namespace { /** - * TODO(SERVER-66036): Expand testing to ensure that `valid()` semantics are in line with "valid - * usage" semantics. - */ - -/** * These tests validate the postconditions of operations on the 4 future types: * - Future * - SemiFuture * - SharedSemiFuture * - ExecutorFuture - * - * TODO(SERVER-66036): use FUTURE_SUCCESS_TEST in all helpers for better coverage of ExecutorFuture. */ -/** Asserts that the Future is still valid() after `func`. */ +/** Asserts that the Future or ExecutorFuture is still valid() after `func`. */ template <typename TestFunc> void assertFutureValidAfter(const TestFunc& func) { FUTURE_SUCCESS_TEST([] { return 0; }, @@ -64,16 +57,38 @@ void assertFutureValidAfter(const TestFunc& func) { }); } -/** Asserts that `func` returns a valid() future while making the input Future non-valid(). */ +/** Asserts that the Future or ExecutorFuture is invalid() after `func`. */ +template <typename TestFunc> +void assertFutureInvalidAfter(const TestFunc& func) { + FUTURE_SUCCESS_TEST([] { return 0; }, + [func](auto&& fut) { + func(std::move(fut)); + ASSERT_FALSE(fut.valid()); + }); +} + +/** + * Asserts that `func` returns a valid() future while making the input Future or ExecutorFuture + * non-valid(). + */ template <DoExecutorFuture doExecutorFuture = kDoExecutorFuture, typename TestFunc> void assertFutureTransfersValid(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto otherFut = func(std::move(fut)); - ASSERT_FALSE(fut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); + FUTURE_SUCCESS_TEST<doExecutorFuture>([] { return 0; }, + [func](auto&& fut) { + auto otherFut = func(std::move(fut)); + ASSERT_FALSE(fut.valid()); + ASSERT_TRUE(otherFut.valid()); + }); +} + +/** Passes an invalid Future or ExecutorFuture into `func`. To be used with DEATH_TEST. */ +template <DoExecutorFuture doExecutorFuture = kDoExecutorFuture, typename TestFunc> +void callWithInvalidFuture(const TestFunc& func) { + FUTURE_SUCCESS_TEST<doExecutorFuture>([] { return 0; }, + [func](auto&& fut) { + [[maybe_unused]] auto val = std::move(fut).get(); + (void)func(std::move(fut)); + }); } TEST(FutureValid, ValidAtStart) { @@ -89,18 +104,62 @@ TEST(FutureValid, ValidAfterGetConstLvalue) { assertFutureValidAfter([](const auto& fut) { [[maybe_unused]] auto val = fut.get(); }); } +DEATH_TEST(FutureValid, GetConstLvalueCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture([](const auto& fut) { [[maybe_unused]] auto val = fut.get(); }); +} + +TEST(FutureValid, InvalidAfterGetRvalue) { + assertFutureInvalidAfter([](auto&& fut) { [[maybe_unused]] auto val = std::move(fut).get(); }); +} + +DEATH_TEST(FutureValid, GetRvalueCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture([](auto&& fut) { [[maybe_unused]] auto val = std::move(fut).get(); }); +} + TEST(FutureValid, ValidAfterGetNoThrowConstLvalue) { assertFutureValidAfter([](const auto& fut) { [[maybe_unused]] auto val = fut.getNoThrow(); }); } +DEATH_TEST(FutureValid, GetNoThrowConstLvalueCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture([](const auto& fut) { [[maybe_unused]] auto val = fut.getNoThrow(); }); +} + +TEST(FutureValid, InvalidAfterGetNoThrowRvalue) { + assertFutureInvalidAfter( + [](auto&& fut) { [[maybe_unused]] auto val = std::move(fut).getNoThrow(); }); +} + +DEATH_TEST(FutureValid, GetNoThrowRvalueCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture( + [](auto&& fut) { [[maybe_unused]] auto val = std::move(fut).getNoThrow(); }); +} + TEST(FutureValid, ValidAfterWait) { assertFutureValidAfter([](auto&& fut) { fut.wait(); }); } +DEATH_TEST(FutureValid, WaitCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture([](auto&& fut) { fut.wait(); }); +} + TEST(FutureValid, ValidAfterWaitNoThrow) { assertFutureValidAfter([](auto&& fut) { [[maybe_unused]] auto status = fut.waitNoThrow(); }); } +DEATH_TEST(FutureValid, WaitNoThrowCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture([](auto&& fut) { [[maybe_unused]] auto status = fut.waitNoThrow(); }); +} + +TEST(FutureValid, ThenTransfersValid) { + assertFutureTransfersValid( + [](auto&& fut) { return std::move(fut).then([](int i) { return i + 2; }); }); +} + +DEATH_TEST(FutureValid, ThenCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture( + [](auto&& fut) { return std::move(fut).then([](int i) { return i + 2; }); }); +} + TEST(FutureValid, ThenRunOnTransfersValid) { assertFutureTransfersValid([](auto&& fut) { auto exec = InlineQueuedCountingExecutor::make(); @@ -108,6 +167,45 @@ TEST(FutureValid, ThenRunOnTransfersValid) { }); } +TEST(FutureValid, InvalidTransfersValid) { + assertFutureTransfersValid([](auto&& fut) { return std::move(fut).ignoreValue(); }); +} + +TEST(FutureValid, InvalidAfterGetAsync) { + assertFutureInvalidAfter([](auto&& fut) { std::move(fut).getAsync([](auto) {}); }); +} + +TEST(FutureValid, OnCompletionTransfersValid) { + assertFutureTransfersValid([](auto&& fut) { return std::move(fut).onCompletion([](auto) {}); }); +} + +TEST(FutureValid, OnErrorTransfersValid) { + assertFutureTransfersValid( + [](auto&& fut) { return std::move(fut).onError([](auto) { return 0; }); }); +} + +TEST(FutureValid, OnErrorCategoryTransfersValid) { + assertFutureTransfersValid([](auto&& fut) { + return std::move(fut).template onErrorCategory<ErrorCategory::NetworkError>( + [](auto) { return 0; }); + }); +} + +TEST(FutureValid, TapTransfersValid) { + assertFutureTransfersValid<kNoExecutorFuture_needsTap>( + [](auto&& fut) { return std::move(fut).tap([](auto) {}); }); +} + +TEST(FutureValid, TapErrorTransfersValid) { + assertFutureTransfersValid<kNoExecutorFuture_needsTap>( + [](auto&& fut) { return std::move(fut).tapError([](auto) {}); }); +} + +TEST(FutureValid, TapAllTransfersValid) { + assertFutureTransfersValid<kNoExecutorFuture_needsTap>( + [](auto&& fut) { return std::move(fut).tapAll([](auto) {}); }); +} + TEST(FutureValid, MoveTransfersValid) { assertFutureTransfersValid([](auto&& fut) { return std::move(fut); }); } @@ -120,6 +218,21 @@ TEST(FutureValid, ShareTransfersValid) { assertFutureTransfersValid([](auto&& fut) { return std::move(fut).share(); }); } +DEATH_TEST(FutureValid, ShareCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidFuture([](auto&& fut) { return std::move(fut).share(); }); +} + +/** Asserts that the SemiFuture is invalid() after `func`. */ +template <typename TestFunc> +void assertSemiFutureInvalidAfter(const TestFunc& func) { + FUTURE_SUCCESS_TEST([] { return 0; }, + [func](auto&& fut) { + auto semiFut = std::move(fut).semi(); + func(std::move(semiFut)); + ASSERT_FALSE(semiFut.valid()); + }); +} + /** Asserts that the SemiFuture is still valid() after `func`. */ template <typename TestFunc> void assertSemiFutureValidAfter(const TestFunc& func) { @@ -134,14 +247,13 @@ void assertSemiFutureValidAfter(const TestFunc& func) { /* Asserts that `func` returns a valid() future while making the input SemiFuture non-valid(). */ template <typename TestFunc> void assertSemiFutureTransfersValid(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto semiFut = std::move(fut).semi(); - auto otherFut = func(std::move(semiFut)); - ASSERT_FALSE(semiFut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); + FUTURE_SUCCESS_TEST([] { return 0; }, + [func](auto&& fut) { + auto semiFut = std::move(fut).semi(); + auto otherFut = func(std::move(semiFut)); + ASSERT_FALSE(semiFut.valid()); + ASSERT_TRUE(otherFut.valid()); + }); } // TODO SERVER-64948: this test is only needed if the lvalue& getter is kept around. @@ -153,11 +265,21 @@ TEST(SemiFutureValid, ValidAfterGetConstLvalue) { assertSemiFutureValidAfter([](const auto& fut) { [[maybe_unused]] auto val = fut.get(); }); } +TEST(SemiFutureValid, InvalidAfterGetRvalue) { + assertSemiFutureInvalidAfter( + [](auto&& fut) { [[maybe_unused]] auto val = std::move(fut).get(); }); +} + TEST(SemiFutureValid, ValidAfterGetNoThrowConstLvalue) { assertSemiFutureValidAfter( [](const auto& fut) { [[maybe_unused]] auto val = fut.getNoThrow(); }); } +TEST(SemiFutureValid, InvalidAfterGetNoThrowRvalue) { + assertSemiFutureInvalidAfter( + [](auto&& fut) { [[maybe_unused]] auto val = std::move(fut).getNoThrow(); }); +} + TEST(SemiFutureValid, ValidAfterWait) { assertSemiFutureValidAfter([](auto&& fut) { fut.wait(); }); } @@ -174,6 +296,10 @@ TEST(SemiFutureValid, ThenRunOnTransfersValid) { }); } +TEST(SemiFutureValid, IgnoreValueTransfersValid) { + assertSemiFutureTransfersValid([](auto&& fut) { return std::move(fut).ignoreValue(); }); +} + TEST(SemiFutureValid, MoveTransfersValid) { assertSemiFutureTransfersValid([](auto&& fut) { return std::move(fut); }); } @@ -207,27 +333,36 @@ void assertSharedSemiFutureValidAfter(const TestFunc& func) { */ template <typename TestFunc> void assertSharedSemiFutureTransfersValid(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto sharedFut = std::move(fut).share(); - auto otherFut = func(std::move(sharedFut)); - ASSERT_FALSE(sharedFut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); + FUTURE_SUCCESS_TEST([] { return 0; }, + [func](auto&& fut) { + auto sharedFut = std::move(fut).share(); + auto otherFut = func(std::move(sharedFut)); + ASSERT_FALSE(sharedFut.valid()); + ASSERT_TRUE(otherFut.valid()); + }); } /** Asserts that `func` returns a valid() Future and keeps the input SharedSemiFuture valid(). */ template <typename TestFunc> void assertSharedSemiFutureSplits(const TestFunc& func) { - // TODO(SERVER-66036): use FUTURE_SUCCESS_TEST once moves from _immediate have the same - // semantics as moves from SharedState - auto [promise, fut] = makePromiseFuture<int>(); - promise.emplaceValue(0); - auto sharedFut = std::move(fut).share(); - auto otherFut = func(std::move(sharedFut)); - ASSERT_TRUE(sharedFut.valid()); // NOLINT - ASSERT_TRUE(otherFut.valid()); + FUTURE_SUCCESS_TEST([] { return 0; }, + [func](auto&& fut) { + auto sharedFut = std::move(fut).share(); + auto otherFut = func(std::move(sharedFut)); + ASSERT_TRUE(sharedFut.valid()); + ASSERT_TRUE(otherFut.valid()); + }); +} + +/** Passes an invalid SharedSemiFuture into `func`. To be used with DEATH_TEST. */ +template <DoExecutorFuture doExecutorFuture = kDoExecutorFuture, typename TestFunc> +void callWithInvalidSharedSemiFuture(const TestFunc& func) { + FUTURE_SUCCESS_TEST<doExecutorFuture>([] { return 0; }, + [func](auto&& fut) { + auto sharedFut = std::move(fut).share(); + auto otherSharedFut = std::move(sharedFut); + (void)func(std::move(sharedFut)); + }); } TEST(SharedSemiFutureValid, ValidAfterGetLvalue) { @@ -265,6 +400,13 @@ TEST(SharedSemiFutureValid, ValidAfterThenRunOn) { }); } +DEATH_TEST(SharedSemiFutureValid, ThenRunOnCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidSharedSemiFuture([](auto&& fut) { + auto exec = InlineQueuedCountingExecutor::make(); + return fut.thenRunOn(exec); + }); +} + TEST(SharedSemiFutureValid, MoveTransfersValid) { assertSharedSemiFutureTransfersValid([](auto&& fut) { return std::move(fut); }); } @@ -273,13 +415,60 @@ TEST(SharedSemiFutureValid, SemiRetainsValid) { assertSharedSemiFutureSplits([](auto&& fut) { return std::move(fut).semi(); }); } +DEATH_TEST(SharedSemiFutureValid, SemiCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidSharedSemiFuture([](auto&& fut) { return std::move(fut).semi(); }); +} + TEST(SharedSemiFutureValid, SplitRetainsValid) { assertSharedSemiFutureSplits([](auto&& fut) { return std::move(fut).split(); }); } +DEATH_TEST(SharedSemiFutureValid, SplitCrashesOnInvalidFuture, "Invariant failure") { + callWithInvalidSharedSemiFuture([](auto&& fut) { return std::move(fut).split(); }); +} + TEST(SharedSemiFutureValid, UnsafeToInlineFutureRetainsValid) { assertSharedSemiFutureSplits([](auto&& fut) { return std::move(fut).unsafeToInlineFuture(); }); } +/* + * Handles the case around an interrupted get() operation. We expect that the Future remains valid + * up until the point where the value is made available. I.e. a continuation can be chained off of + * a Future whose get() has been interrupted because the caller never gets access to the value. + */ +TEST(FutureValid, InterruptedGetValidity) { + FUTURE_SUCCESS_TEST([] { return 0; }, + [](auto&& fut) { + const auto exec = InlineRecursiveCountingExecutor::make(); + DummyInterruptible dummyInterruptible; + + auto res = std::move(fut).getNoThrow(&dummyInterruptible); + + if (!res.isOK()) { + ASSERT_EQ(res.getStatus(), ErrorCodes::Interrupted); + ASSERT_TRUE(fut.valid()); + } else { + ASSERT_FALSE(fut.valid()); + } + }); +} +TEST(SemiFutureValid, InterruptedGetValidity) { + FUTURE_SUCCESS_TEST([] { return 0; }, + [](auto&& fut) { + auto semiFut = std::move(fut).semi(); + const auto exec = InlineRecursiveCountingExecutor::make(); + DummyInterruptible dummyInterruptible; + + auto res = std::move(semiFut).getNoThrow(&dummyInterruptible); + + if (!res.isOK()) { + ASSERT_EQ(res.getStatus(), ErrorCodes::Interrupted); + ASSERT_TRUE(semiFut.valid()); + } else { + ASSERT_FALSE(semiFut.valid()); + } + }); +} + } // namespace } // namespace mongo |
