diff options
author | Tyler Seip <Tyler.Seip@mongodb.com> | 2021-02-05 00:02:30 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-02-18 08:06:06 +0000 |
commit | 25818a31c7d8209aa8ae22a536f94725aa7fa21e (patch) | |
tree | 80c520b180ec0810c9a048699a3aa8c002721779 /src | |
parent | 34eb3c85c8c165877bc45dd40621ab5bab7869f5 (diff) | |
download | mongo-25818a31c7d8209aa8ae22a536f94725aa7fa21e.tar.gz |
SERVER-53558: Create variadic template implementations of when* future utilities
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/util/future_util.h | 43 | ||||
-rw-r--r-- | src/mongo/util/future_util_test.cpp | 115 |
2 files changed, 158 insertions, 0 deletions
diff --git a/src/mongo/util/future_util.h b/src/mongo/util/future_util.h index 25865caae89..9aa294a79e3 100644 --- a/src/mongo/util/future_util.h +++ b/src/mongo/util/future_util.h @@ -357,6 +357,18 @@ inline constexpr bool isFutureOrExecutorFuture<ExecutorFuture<T>> = true; static inline const std::string kWhenAllSucceedEmptyInputInvariantMsg = "Must pass at least one future to whenAllSucceed"; +/** + * Turns a variadic parameter pack into a vector without invoking copies if possible via static + * casts. + */ +template <typename... U, typename T = std::common_type_t<U...>> +std::vector<T> variadicArgsToVector(U&&... elems) { + std::vector<T> vector; + vector.reserve(sizeof...(elems)); + (vector.push_back(std::forward<U>(elems)), ...); + return vector; +} + } // namespace future_util_details /** @@ -603,6 +615,36 @@ SemiFuture<Result> whenAny(std::vector<FutureT>&& futures) { return std::move(future).semi(); } +/** + * Variadic template overloads for the above helper functions. Though not strictly necessary, + * we peel off the first element of each input list in order to assist the compiler in type + * inference and to prevent 0 length lists from compiling. + */ +TEMPLATE(typename... FuturePack, + typename FutureLike = std::common_type_t<FuturePack...>, + typename Value = typename FutureLike::value_type, + typename ResultVector = std::vector<Value>) +REQUIRES(future_util_details::isFutureOrExecutorFuture<FutureLike>) +auto whenAllSucceed(FuturePack&&... futures) { + return whenAllSucceed( + future_util_details::variadicArgsToVector(std::forward<FuturePack>(futures)...)); +} + +template <typename... FuturePack, + typename FutureT = std::common_type_t<FuturePack...>, + typename Value = typename FutureT::value_type, + typename ResultVector = std::vector<StatusOrStatusWith<Value>>> +SemiFuture<ResultVector> whenAll(FuturePack&&... futures) { + return whenAll(future_util_details::variadicArgsToVector(std::forward<FuturePack>(futures)...)); +} + +template <typename... FuturePack, + typename FutureT = std::common_type_t<FuturePack...>, + typename Result = WhenAnyResult<typename FutureT::value_type>> +SemiFuture<Result> whenAny(FuturePack&&... futures) { + return whenAny(future_util_details::variadicArgsToVector(std::forward<FuturePack>(futures)...)); +} + namespace future_util { /** @@ -690,4 +732,5 @@ auto makeState(Args&&... args) { } } // namespace future_util + } // namespace mongo diff --git a/src/mongo/util/future_util_test.cpp b/src/mongo/util/future_util_test.cpp index 824591dccbc..fce8e9c696a 100644 --- a/src/mongo/util/future_util_test.cpp +++ b/src/mongo/util/future_util_test.cpp @@ -664,6 +664,40 @@ TEST_F(WhenAllSucceedTest, WhenAllSucceedWorksWithExecutorFutures) { } } +TEST_F(WhenAllSucceedTest, VariadicWhenAllSucceedMaintainsOrderingOfInputFutures) { + const auto kNumInputs = 5; + auto [inputPromises, inputFutures] = makePromisesAndFutures<int>(kNumInputs); + + auto result = whenAllSucceed(std::move(inputFutures[0]), + std::move(inputFutures[1]), + std::move(inputFutures[2]), + std::move(inputFutures[3]), + std::move(inputFutures[4])); + + // Create a random order of indexes in which to resolve the input futures. + std::vector<int> ordering; + for (auto i = 0; i < kNumInputs; ++i) { + ordering.push_back(i); + } + std::random_device rd; + std::mt19937 g(rd()); + std::shuffle(ordering.begin(), ordering.end(), g); + + for (auto idx : ordering) { + ASSERT_FALSE(result.isReady()); + inputPromises[idx].emplaceValue(idx); + } + + auto outputValues = result.get(); + ASSERT_EQ(outputValues.size(), kNumInputs); + + // The output should be in the same order as the input, regardless of the + // order in which the futures resolved. + for (auto i = 0; i < kNumInputs; ++i) { + ASSERT_EQ(outputValues[i], i); + } +} + // Test whenAllSucceed with void input futures. using WhenAllSucceedVoidTest = WhenAllSucceedTest; @@ -763,6 +797,25 @@ TEST_F(WhenAllSucceedVoidTest, ASSERT_EQ(result.getNoThrow(), Status::OK()); } +TEST_F(WhenAllSucceedVoidTest, + VariadicWhenAllSucceedVoidReturnsReturnsAfterLastSuccessfulResponseWithManyInputFutures) { + const auto kNumInputs = 5; + auto [inputPromises, inputFutures] = makePromisesAndFutures<void>(kNumInputs); + + auto result = whenAllSucceed(std::move(inputFutures[0]), + std::move(inputFutures[1]), + std::move(inputFutures[2]), + std::move(inputFutures[3]), + std::move(inputFutures[4])); + + for (auto i = 0; i < kNumInputs; ++i) { + ASSERT_FALSE(result.isReady()); + inputPromises[i].emplaceValue(); + } + + ASSERT_EQ(result.getNoThrow(), Status::OK()); +} + TEST_F(WhenAllSucceedVoidTest, WhenAllSucceedWorksWithExecutorFutures) { const auto kNumInputs = 5; auto [inputPromises, rawInputFutures] = makePromisesAndFutures<void>(kNumInputs); @@ -954,6 +1007,28 @@ TEST_F(WhenAllTest, WorksWithExecutorFutures) { } } +TEST_F(WhenAllTest, WorksWithVariadicTemplate) { + const auto kNumInputs = 3; + auto [inputPromises, inputFutures] = makePromisesAndFutures<int>(kNumInputs); + + auto result = + whenAll(std::move(inputFutures[0]), std::move(inputFutures[1]), std::move(inputFutures[2])); + ASSERT_FALSE(result.isReady()); + + const auto kValue = 10; + for (auto i = 0; i < kNumInputs; ++i) { + ASSERT_FALSE(result.isReady()); + inputPromises[i].emplaceValue(kValue); + } + + auto output = result.get(); + ASSERT_EQ(output.size(), kNumInputs); + for (auto& swValue : output) { + ASSERT_TRUE(swValue.isOK()); + ASSERT_EQ(swValue.getValue(), kValue); + } +} + class WhenAnyTest : public FutureUtilTest {}; TEST_F(WhenAnyTest, ReturnsTheFirstFutureToResolveWhenThatFutureContainsSuccessAndOnlyOneInput) { @@ -1114,6 +1189,46 @@ TEST_F(WhenAnyTest, WorksWithExecutorFutures) { ASSERT_EQ(idx, kWhichIdxWillBeFirst); } +TEST_F(WhenAnyTest, WorksWithVariadicTemplate) { + const auto kNumInputs = 3; + auto [inputPromises, inputFutures] = makePromisesAndFutures<int>(kNumInputs); + + auto result = + whenAny(std::move(inputFutures[0]), std::move(inputFutures[1]), std::move(inputFutures[2])); + ASSERT_FALSE(result.isReady()); + + const auto kWhichIdxWillBeFirst = 1; + const auto kValue = 10; + inputPromises[kWhichIdxWillBeFirst].emplaceValue(kValue); + auto [swVal, idx] = result.get(); + ASSERT_TRUE(swVal.isOK()); + ASSERT_EQ(swVal.getValue(), kValue); + ASSERT_EQ(idx, kWhichIdxWillBeFirst); +} + +TEST_F(WhenAnyTest, WorksWithVariadicTemplateAndExecutorFutures) { + const auto kNumInputs = 3; + auto [inputPromises, rawInputFutures] = makePromisesAndFutures<int>(kNumInputs); + + // Turn raw input Futures into ExecutorFutures. + std::vector<ExecutorFuture<int>> inputFutures; + for (auto i = 0; i < kNumInputs; ++i) { + inputFutures.emplace_back(std::move(rawInputFutures[i]).thenRunOn(executor())); + } + + auto result = + whenAny(std::move(inputFutures[0]), std::move(inputFutures[1]), std::move(inputFutures[2])); + ASSERT_FALSE(result.isReady()); + + const auto kWhichIdxWillBeFirst = 1; + const auto kValue = 10; + inputPromises[kWhichIdxWillBeFirst].emplaceValue(kValue); + auto [swVal, idx] = result.get(); + ASSERT_TRUE(swVal.isOK()); + ASSERT_EQ(swVal.getValue(), kValue); + ASSERT_EQ(idx, kWhichIdxWillBeFirst); +} + class AsyncStateTest : public FutureUtilTest { public: class SettingGuard { |