summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMatt Diener <matt.diener@mongodb.com>2022-09-06 16:55:28 +0000
committerMongoDB Bot <mongo-bot@mongodb.com>2024-02-13 19:38:15 +0000
commit17c0d8bbee46f15f1574079e266a9997cebe6d0e (patch)
treeedae1dcbe1e46d4f5c91dd4c40c8d1d5fb0e322b /src
parent5bae18f9e299f3d02e34f93d05ba34a40871e29e (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.h15
-rw-r--r--src/mongo/util/future_impl.h97
-rw-r--r--src/mongo/util/future_test_valid.cpp269
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