summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTyler Seip <Tyler.Seip@mongodb.com>2021-02-05 00:02:30 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-02-18 08:06:06 +0000
commit25818a31c7d8209aa8ae22a536f94725aa7fa21e (patch)
tree80c520b180ec0810c9a048699a3aa8c002721779 /src
parent34eb3c85c8c165877bc45dd40621ab5bab7869f5 (diff)
downloadmongo-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.h43
-rw-r--r--src/mongo/util/future_util_test.cpp115
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 {