Commit 6f3d8f43 authored by Lee Howes's avatar Lee Howes Committed by Facebook Github Bot

Add Unsafe version of collect functions to ease migration to safe SemiFuture versions

Summary:
Migration from Future-returning executor-erasing collectX forms to SemiFuture-returning forms, that are less risky in particular with coroutines.

An earlier change added collectXSemiFuture. This diff adds collectXUnsafe as a migration path.

Reviewed By: yfeldblum

Differential Revision: D20255061

fbshipit-source-id: 379b5ed95aa7782aa121dba8e84cb48f802a57cd
parent 9db0f005
...@@ -1416,6 +1416,12 @@ Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>> collectAll( ...@@ -1416,6 +1416,12 @@ Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>> collectAll(
return collectAllSemiFuture(std::forward<Fs>(fs)...).toUnsafeFuture(); return collectAllSemiFuture(std::forward<Fs>(fs)...).toUnsafeFuture();
} }
template <typename... Fs>
Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>>
collectAllUnsafe(Fs&&... fs) {
return collectAllSemiFuture(std::forward<Fs>(fs)...).toUnsafeFuture();
}
// collectAll (iterator) // collectAll (iterator)
template <class InputIterator> template <class InputIterator>
...@@ -1465,6 +1471,13 @@ collectAllSemiFuture(InputIterator first, InputIterator last) { ...@@ -1465,6 +1471,13 @@ collectAllSemiFuture(InputIterator first, InputIterator last) {
return future; return future;
} }
template <class InputIterator>
Future<std::vector<
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
collectAllUnsafe(InputIterator first, InputIterator last) {
return collectAllSemiFuture(first, last).toUnsafeFuture();
}
template <class InputIterator> template <class InputIterator>
Future<std::vector< Future<std::vector<
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>> Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
...@@ -1531,6 +1544,13 @@ collectSemiFuture(InputIterator first, InputIterator last) { ...@@ -1531,6 +1544,13 @@ collectSemiFuture(InputIterator first, InputIterator last) {
return future; return future;
} }
template <class InputIterator>
Future<std::vector<
typename std::iterator_traits<InputIterator>::value_type::value_type>>
collectUnsafe(InputIterator first, InputIterator last) {
return collectSemiFuture(first, last).toUnsafeFuture();
}
template <class InputIterator> template <class InputIterator>
Future<std::vector< Future<std::vector<
typename std::iterator_traits<InputIterator>::value_type::value_type>> typename std::iterator_traits<InputIterator>::value_type::value_type>>
...@@ -1592,6 +1612,12 @@ Future<std::tuple<typename remove_cvref_t<Fs>::value_type...>> collect( ...@@ -1592,6 +1612,12 @@ Future<std::tuple<typename remove_cvref_t<Fs>::value_type...>> collect(
return collectSemiFuture(std::forward<Fs>(fs)...).toUnsafeFuture(); return collectSemiFuture(std::forward<Fs>(fs)...).toUnsafeFuture();
} }
template <typename... Fs>
Future<std::tuple<typename remove_cvref_t<Fs>::value_type...>> collectUnsafe(
Fs&&... fs) {
return collectSemiFuture(std::forward<Fs>(fs)...).toUnsafeFuture();
}
template <class Collection> template <class Collection>
auto collectSemiFuture(Collection&& c) auto collectSemiFuture(Collection&& c)
-> decltype(collectSemiFuture(c.begin(), c.end())) { -> decltype(collectSemiFuture(c.begin(), c.end())) {
...@@ -1609,6 +1635,14 @@ collectAny(InputIterator first, InputIterator last) { ...@@ -1609,6 +1635,14 @@ collectAny(InputIterator first, InputIterator last) {
return collectAnySemiFuture(first, last).via(&InlineExecutor::instance()); return collectAnySemiFuture(first, last).via(&InlineExecutor::instance());
} }
template <class InputIterator>
Future<std::pair<
size_t,
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
collectAnyUnsafe(InputIterator first, InputIterator last) {
return collectAnySemiFuture(first, last).via(&InlineExecutor::instance());
}
template <class InputIterator> template <class InputIterator>
SemiFuture<std::pair< SemiFuture<std::pair<
size_t, size_t,
......
...@@ -2363,6 +2363,17 @@ auto collectAllSemiFuture(Collection&& c) ...@@ -2363,6 +2363,17 @@ auto collectAllSemiFuture(Collection&& c)
return collectAllSemiFuture(c.begin(), c.end()); return collectAllSemiFuture(c.begin(), c.end());
} }
template <class InputIterator>
Future<std::vector<
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
collectAllUnsafe(InputIterator first, InputIterator last);
template <class Collection>
auto collectAllUnsafe(Collection&& c)
-> decltype(collectAllUnsafe(c.begin(), c.end())) {
return collectAllUnsafe(c.begin(), c.end());
}
template <class InputIterator> template <class InputIterator>
Future<std::vector< Future<std::vector<
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>> Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
...@@ -2381,12 +2392,29 @@ template <typename... Fs> ...@@ -2381,12 +2392,29 @@ template <typename... Fs>
SemiFuture<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>> SemiFuture<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>>
collectAllSemiFuture(Fs&&... fs); collectAllSemiFuture(Fs&&... fs);
template <typename... Fs>
Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>>
collectAllUnsafe(Fs&&... fs);
template <typename... Fs> template <typename... Fs>
Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>> collectAll( Future<std::tuple<Try<typename remove_cvref_t<Fs>::value_type>...>> collectAll(
Fs&&... fs); Fs&&... fs);
/// Like collectAll, but will short circuit on the first exception. Thus, the /// Like collectAll, but will short circuit on the first exception. Thus, the
/// type of the returned Future is std::vector<T> instead of /// type of the returned Future is std::vector<T> instead of
/// std::vector<Try<T>> /// std::vector<Try<T>>
template <class InputIterator>
Future<std::vector<
typename std::iterator_traits<InputIterator>::value_type::value_type>>
collectUnsafe(InputIterator first, InputIterator last);
/// Sugar for the most common case
template <class Collection>
auto collectUnsafe(Collection&& c)
-> decltype(collectUnsafe(c.begin(), c.end())) {
return collectUnsafe(c.begin(), c.end());
}
template <class InputIterator> template <class InputIterator>
Future<std::vector< Future<std::vector<
typename std::iterator_traits<InputIterator>::value_type::value_type>> typename std::iterator_traits<InputIterator>::value_type::value_type>>
...@@ -2417,6 +2445,11 @@ Future<std::pair< ...@@ -2417,6 +2445,11 @@ Future<std::pair<
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>> Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
collectAny(InputIterator first, InputIterator last); collectAny(InputIterator first, InputIterator last);
template <class InputIterator> template <class InputIterator>
Future<std::pair<
size_t,
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
collectAnyUnsafe(InputIterator first, InputIterator last);
template <class InputIterator>
SemiFuture<std::pair< SemiFuture<std::pair<
size_t, size_t,
Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>> Try<typename std::iterator_traits<InputIterator>::value_type::value_type>>>
...@@ -2428,6 +2461,11 @@ auto collectAny(Collection&& c) -> decltype(collectAny(c.begin(), c.end())) { ...@@ -2428,6 +2461,11 @@ auto collectAny(Collection&& c) -> decltype(collectAny(c.begin(), c.end())) {
return collectAny(c.begin(), c.end()); return collectAny(c.begin(), c.end());
} }
template <class Collection> template <class Collection>
auto collectAnyUnsafe(Collection&& c)
-> decltype(collectAnyUnsafe(c.begin(), c.end())) {
return collectAnyUnsafe(c.begin(), c.end());
}
template <class Collection>
auto collectAnySemiFuture(Collection&& c) auto collectAnySemiFuture(Collection&& c)
-> decltype(collectAnySemiFuture(c.begin(), c.end())) { -> decltype(collectAnySemiFuture(c.begin(), c.end())) {
return collectAnySemiFuture(c.begin(), c.end()); return collectAnySemiFuture(c.begin(), c.end());
......
...@@ -112,6 +112,87 @@ TEST(Collect, collectAll) { ...@@ -112,6 +112,87 @@ TEST(Collect, collectAll) {
} }
} }
TEST(Collect, collectAllUnsafe) {
// returns a vector variant
{
std::vector<Promise<int>> promises(10);
std::vector<Future<int>> futures;
for (auto& p : promises) {
futures.push_back(p.getFuture());
}
auto allf = collectAllUnsafe(futures);
std::shuffle(promises.begin(), promises.end(), rng);
for (auto& p : promises) {
EXPECT_FALSE(allf.isReady());
p.setValue(42);
}
EXPECT_TRUE(allf.isReady());
auto& results = allf.value();
for (auto& t : results) {
EXPECT_EQ(42, t.value());
}
}
// check error semantics
{
std::vector<Promise<int>> promises(4);
std::vector<Future<int>> futures;
for (auto& p : promises) {
futures.push_back(p.getFuture());
}
auto allf = collectAllUnsafe(futures);
promises[0].setValue(42);
promises[1].setException(eggs);
EXPECT_FALSE(allf.isReady());
promises[2].setValue(42);
EXPECT_FALSE(allf.isReady());
promises[3].setException(eggs);
EXPECT_TRUE(allf.isReady());
EXPECT_FALSE(allf.getTry().hasException());
auto& results = allf.value();
EXPECT_EQ(42, results[0].value());
EXPECT_TRUE(results[1].hasException());
EXPECT_EQ(42, results[2].value());
EXPECT_TRUE(results[3].hasException());
}
// check that futures are ready in thenValue()
{
std::vector<Promise<Unit>> promises(10);
std::vector<Future<Unit>> futures;
for (auto& p : promises) {
futures.push_back(p.getFuture());
}
auto allf = collectAllSemiFuture(futures).toUnsafeFuture().thenTry(
[](Try<std::vector<Try<Unit>>>&& ts) {
for (auto& f : ts.value()) {
f.value();
}
});
std::shuffle(promises.begin(), promises.end(), rng);
for (auto& p : promises) {
p.setValue();
}
EXPECT_TRUE(allf.isReady());
}
}
TEST(Collect, collectAllSemiFutureInline) { TEST(Collect, collectAllSemiFutureInline) {
// inline future collection on same executor // inline future collection on same executor
{ {
...@@ -364,6 +445,33 @@ TEST(Collect, collectAny) { ...@@ -364,6 +445,33 @@ TEST(Collect, collectAny) {
auto& f = idx_fut.second; auto& f = idx_fut.second;
EXPECT_EQ(42, f.value()); EXPECT_EQ(42, f.value());
} }
{
std::vector<Promise<int>> promises(10);
std::vector<Future<int>> futures;
for (auto& p : promises) {
futures.push_back(p.getFuture());
}
for (auto& f : futures) {
EXPECT_FALSE(f.isReady());
}
auto anyf = collectAnyUnsafe(futures);
/* futures were moved in, so these are invalid now */
EXPECT_FALSE(anyf.isReady());
promises[7].setValue(42);
EXPECT_TRUE(anyf.isReady());
auto& idx_fut = anyf.value();
auto i = idx_fut.first;
EXPECT_EQ(7, i);
auto& f = idx_fut.second;
EXPECT_EQ(42, f.value());
}
{ {
std::vector<Promise<int>> promises(10); std::vector<Promise<int>> promises(10);
std::vector<SemiFuture<int>> futures; std::vector<SemiFuture<int>> futures;
...@@ -524,6 +632,15 @@ TEST(Collect, collectAnyWithoutException) { ...@@ -524,6 +632,15 @@ TEST(Collect, collectAnyWithoutException) {
} }
TEST(Collect, alreadyCompleted) { TEST(Collect, alreadyCompleted) {
{
std::vector<Future<Unit>> fs;
for (int i = 0; i < 10; i++) {
fs.push_back(makeFuture());
}
collectAllUnsafe(fs).thenValue(
[&](std::vector<Try<Unit>> ts) { EXPECT_EQ(fs.size(), ts.size()); });
}
{ {
std::vector<Future<Unit>> fs; std::vector<Future<Unit>> fs;
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
...@@ -789,6 +906,26 @@ TEST(Collect, collectAllVariadic) { ...@@ -789,6 +906,26 @@ TEST(Collect, collectAllVariadic) {
EXPECT_TRUE(flag); EXPECT_TRUE(flag);
} }
TEST(Collect, collectAllUnsafeVariadic) {
Promise<bool> pb;
Promise<int> pi;
Future<bool> fb = pb.getFuture();
Future<int> fi = pi.getFuture();
bool flag = false;
collectAllUnsafe(std::move(fb), std::move(fi))
.thenValue([&](std::tuple<Try<bool>, Try<int>> tup) {
flag = true;
EXPECT_TRUE(std::get<0>(tup).hasValue());
EXPECT_EQ(std::get<0>(tup).value(), true);
EXPECT_TRUE(std::get<1>(tup).hasValue());
EXPECT_EQ(std::get<1>(tup).value(), 42);
});
pb.setValue(true);
EXPECT_FALSE(flag);
pi.setValue(42);
EXPECT_TRUE(flag);
}
TEST(Collect, collectAllVariadicReferences) { TEST(Collect, collectAllVariadicReferences) {
Promise<bool> pb; Promise<bool> pb;
Promise<int> pi; Promise<int> pi;
...@@ -848,6 +985,24 @@ TEST(Collect, collectVariadic) { ...@@ -848,6 +985,24 @@ TEST(Collect, collectVariadic) {
EXPECT_TRUE(flag); EXPECT_TRUE(flag);
} }
TEST(Collect, collectUnsafeVariadic) {
Promise<bool> pb;
Promise<int> pi;
Future<bool> fb = pb.getFuture();
Future<int> fi = pi.getFuture();
bool flag = false;
collectUnsafe(std::move(fb), std::move(fi))
.thenValue([&](std::tuple<bool, int> tup) {
flag = true;
EXPECT_EQ(std::get<0>(tup), true);
EXPECT_EQ(std::get<1>(tup), 42);
});
pb.setValue(true);
EXPECT_FALSE(flag);
pi.setValue(42);
EXPECT_TRUE(flag);
}
TEST(Collect, collectVariadicWithException) { TEST(Collect, collectVariadicWithException) {
Promise<bool> pb; Promise<bool> pb;
Promise<int> pi; Promise<int> pi;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment