Commit ea7aeab5 authored by Cameron Pickett's avatar Cameron Pickett Committed by Facebook GitHub Bot

Implement range-accepting versions of collectAny*

Reviewed By: iahs

Differential Revision: D31233377

fbshipit-source-id: c61de3936ad2c3d573ef71aca385dda023b38ee7
parent 8ccd13f0
......@@ -1056,6 +1056,98 @@ auto collectAnyNoDiscard(SemiAwaitables&&... awaitables)
static_cast<SemiAwaitables&&>(awaitables)...);
}
template <typename InputRange>
auto collectAnyRange(InputRange awaitables) -> folly::coro::Task<std::pair<
size_t,
folly::Try<detail::collect_all_range_component_t<
detail::range_reference_t<InputRange>>>>> {
const CancellationToken& parentCancelToken =
co_await co_current_cancellation_token;
const CancellationSource cancelSource;
const CancellationToken cancelToken =
CancellationToken::merge(parentCancelToken, cancelSource.getToken());
std::pair<
size_t,
folly::Try<detail::collect_all_range_component_t<
detail::range_reference_t<InputRange>>>>
firstCompletion;
firstCompletion.first = size_t(-1);
using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
auto makeTask = [&](awaitable_type semiAwaitable,
size_t index) -> folly::coro::Task<void> {
auto result = co_await folly::coro::co_awaitTry(std::move(semiAwaitable));
if (!cancelSource.requestCancellation()) {
// This is first entity to request cancellation.
firstCompletion.first = index;
firstCompletion.second = std::move(result);
}
};
// Create a task to await each input awaitable.
std::vector<folly::coro::Task<void>> tasks;
// TODO: Detect when the input range supports constant-time
// .size() and pre-reserve storage for that many elements in 'tasks'.
size_t taskCount = 0;
for (auto&& semiAwaitable : static_cast<InputRange&&>(awaitables)) {
tasks.push_back(makeTask(
static_cast<decltype(semiAwaitable)&&>(semiAwaitable), taskCount++));
}
co_await folly::coro::co_withCancellation(
cancelToken, folly::coro::collectAllRange(tasks | ranges::views::move));
if (parentCancelToken.isCancellationRequested()) {
co_yield co_cancelled;
}
co_return firstCompletion;
}
template <typename InputRange>
auto collectAnyNoDiscardRange(InputRange awaitables)
-> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
detail::range_reference_t<InputRange>>>> {
const CancellationToken& parentCancelToken =
co_await co_current_cancellation_token;
const CancellationSource cancelSource;
const CancellationToken cancelToken =
CancellationToken::merge(parentCancelToken, cancelSource.getToken());
std::vector<detail::collect_all_try_range_component_t<
detail::range_reference_t<InputRange>>>
results;
using awaitable_type = remove_cvref_t<detail::range_reference_t<InputRange>>;
auto makeTask = [&](awaitable_type semiAwaitable,
size_t index) -> folly::coro::Task<void> {
auto result = co_await folly::coro::co_awaitTry(std::move(semiAwaitable));
cancelSource.requestCancellation();
results[index] = std::move(result);
};
// Create a task to await each input awaitable.
std::vector<folly::coro::Task<void>> tasks;
// TODO: Detect when the input range supports constant-time
// .size() and pre-reserve storage for that many elements in 'tasks'.
size_t taskCount = 0;
for (auto&& semiAwaitable : static_cast<InputRange&&>(awaitables)) {
tasks.push_back(makeTask(
static_cast<decltype(semiAwaitable)&&>(semiAwaitable), taskCount++));
}
results.resize(taskCount);
co_await folly::coro::co_withCancellation(
cancelToken, folly::coro::collectAllRange(tasks | ranges::views::move));
co_return results;
}
} // namespace coro
} // namespace folly
......
......@@ -434,7 +434,7 @@ auto collectAllTryWindowed(
// folly::coro::Task<Foo> getDataOneWay();
// folly::coro::Task<Foo> getDataAnotherWay();
//
// std::pair<std::size_t, Foo> result = co_await folly::coro::collectAny(
// std::pair<std::size_t, Try<Foo>> result = co_await folly::coro::collectAny(
// getDataOneWay(), getDataAnotherWay());
//
template <typename SemiAwaitable, typename... SemiAwaitables>
......@@ -465,13 +465,90 @@ auto collectAny(SemiAwaitable&& awaitable, SemiAwaitables&&... awaitables)
// folly::coro::Task<Bar> getDataAnotherWay();
//
// std::tuple<folly::Try<Foo>, folly::Try<Bar>> result = co_await
// folly::coro::collectAny(getDataOneWay(), getDataAnotherWay());
// folly::coro::collectAnyNoDiscard(getDataOneWay(), getDataAnotherWay());
//
template <typename... SemiAwaitables>
auto collectAnyNoDiscard(SemiAwaitables&&... awaitables)
-> folly::coro::Task<std::tuple<detail::collect_all_try_component_t<
remove_cvref_t<SemiAwaitables>>...>>;
///////////////////////////////////////////////////////////////////////////
// collectAnyRange(RangeOf<SemiAwaitable<T>>&&)
// -> SemiAwaitable<std::pair<std::size_t, folly::Try<T>>>
//
// The collectAnyRange() function can be used to concurrently co_await on
// multiple SemiAwaitable objects, get the result and index of the first one
// completing, cancel the remaining ones and continue once they are completed.
//
// collectAnyRange() accepts zero or more SemiAwaitable objects and
// returns a SemiAwaitable object that will complete with a pair containing the
// result of the first one to complete and its index.
//
// collectAnyRange() is built on top of collectAllRange(), be aware of the
// coroutine starting behavior described in collectAll() documentation.
//
// The result of the first SemiAwaitable is going to be returned, whether it
// is a value or an exception. Any result of the remaining SemiAwaitables will
// be discarded, independently of whether it's a value or an exception.
//
// e.g.
//
// std::vector<Task<T>> tasks = ...;
// std::pair<size_t, Try<T>> result = co_await collectAnyRange(tasks |
// ranges::views::move);
//
template <typename InputRange>
auto collectAnyRange(InputRange awaitables) -> folly::coro::Task<std::pair<
size_t,
folly::Try<detail::collect_all_range_component_t<
detail::range_reference_t<InputRange>>>>>;
///////////////////////////////////////////////////////////////////////////
// collectAnyNoDiscardRange(RangeOf<SemiAwaitable<T>>&&)
// -> SemiAwaitable<std::vector<folly::Try<T>>>
//
// The collectAnyNoDiscardRange() function is similar to collectAnyRange() in
// that it co_awaits multiple SemiAwaitables and cancels any outstanding
// operations once at least one has finished. Unlike collectAnyRange(), it
// returns results from *all* SemiAwaitables, including
// folly::OperationCancelled for operations that were cancelled.
//
// collectAnyNoDiscardRange() is built on top of collectAllRange(), be aware of
// the coroutine starting behavior described in collectAll() documentation.
//
// The success/failure of individual results can be inspected by calling
// .hasValue() or .hasException() on the elements of the returned vector.
//
// Example:
// folly::coro::Task<Foo> getDataOneWay();
// folly::coro::Task<Foo> getDataAnotherWay();
//
// std::vector<folly::Try<Foo>> result = co_await
// folly::coro::collectAnyNoDiscard(getDataOneWay(), getDataAnotherWay());
//
template <typename InputRange>
auto collectAnyNoDiscardRange(InputRange awaitables)
-> folly::coro::Task<std::vector<detail::collect_all_try_range_component_t<
detail::range_reference_t<InputRange>>>>;
// collectAnyRange()/collectAnyNoDiscardRange() overloads that simplifies the
// common-case where an rvalue std::vector<SemiAwaitable> is passed.
//
// This avoids the caller needing to pipe the input through ranges::views::move
// transform to force the elements to be rvalue-references since the
// std::vector<T>::reference type is T& rather than T&& and some awaitables,
// such as Task<U>, are not lvalue awaitable.
template <typename SemiAwaitable>
auto collectAnyRange(std::vector<SemiAwaitable> awaitables)
-> decltype(collectAnyRange(awaitables | ranges::views::move)) {
co_return co_await collectAnyRange(awaitables | ranges::views::move);
}
template <typename SemiAwaitable>
auto collectAnyNoDiscardRange(std::vector<SemiAwaitable> awaitables)
-> decltype(collectAnyNoDiscardRange(awaitables | ranges::views::move)) {
co_return co_await collectAnyNoDiscardRange(awaitables | ranges::views::move);
}
} // namespace coro
} // namespace folly
......
......@@ -2313,4 +2313,543 @@ TEST_F(CollectAnyNoDiscardTest, CancellationTokenRemainsActiveAfterReturn) {
}());
}
class CollectAnyRangeTest : public testing::Test {};
TEST_F(CollectAnyRangeTest, OneTaskWithValue) {
folly::coro::Baton baton;
auto f = [&]() -> folly::coro::Task<std::string> {
co_await baton;
co_return "hello";
};
bool completed = false;
auto run = [&]() -> folly::coro::Task<void> {
std::vector<folly::coro::Task<std::string>> tasks;
tasks.push_back(f());
auto [index, result] =
co_await folly::coro::collectAnyRange(std::move(tasks));
CHECK_EQ("hello", result.value());
CHECK_EQ(0, index);
completed = true;
};
folly::ManualExecutor executor;
auto future = run().scheduleOn(&executor).start();
executor.drain();
CHECK(!completed);
baton.post();
// Posting the baton should have just scheduled the 'f()' coroutine
// for resumption on the executor but should not have executed
// until we drain the executor again.
CHECK(!completed);
executor.drain();
CHECK(completed);
CHECK(future.isReady());
}
TEST_F(CollectAnyRangeTest, OneVoidTask) {
bool completed = false;
folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
// Checks that the task actually runs and that 'void' results are
// promoted to folly::Unit when placed in a tuple.
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<void>&&> {
co_yield [&]() -> folly::coro::Task<void> {
completed = true;
co_return;
}();
};
std::pair<std::size_t, folly::Try<void>> result =
co_await folly::coro::collectAnyRange(generateTasks());
(void)result;
}());
CHECK(completed);
}
TEST_F(CollectAnyRangeTest, MoveOnlyType) {
bool completed = false;
folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
// Checks that the task actually runs and that move only results
// can be correctly returned
auto generateTasks = [&]()
-> folly::coro::Generator<folly::coro::Task<std::unique_ptr<int>>&&> {
co_yield [&]() -> folly::coro::Task<std::unique_ptr<int>> {
completed = true;
co_return std::make_unique<int>(1);
}();
};
std::pair<std::size_t, folly::Try<std::unique_ptr<int>>> result =
co_await folly::coro::collectAnyRange(generateTasks());
(void)result;
}());
CHECK(completed);
}
TEST_F(CollectAnyRangeTest, CollectAnyDoesntCompleteUntilAllTasksComplete) {
folly::coro::Baton baton1;
folly::coro::Baton baton2;
bool task1Started = false;
bool task2Started = false;
bool complete = false;
constexpr std::size_t taskTerminatingExpectedIndex = 1;
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<int>&&> {
co_yield [&]() -> folly::coro::Task<int> {
task1Started = true;
co_await baton1;
co_return 42;
}();
co_yield [&]() -> folly::coro::Task<int> {
task2Started = true;
co_await baton2;
co_return 314;
}();
};
auto run = [&]() -> folly::coro::Task<void> {
auto [index, result] =
co_await folly::coro::collectAnyRange(generateTasks());
complete = true;
CHECK_EQ(taskTerminatingExpectedIndex, index);
CHECK_EQ(result.value(), 314);
};
folly::ManualExecutor executor;
auto future = run().scheduleOn(&executor).start();
CHECK(!task1Started);
CHECK(!task2Started);
executor.drain();
CHECK(task1Started);
CHECK(task2Started);
CHECK(!complete);
baton2.post();
executor.drain();
CHECK(!complete);
baton1.post();
executor.drain();
CHECK(complete);
CHECK(future.isReady());
}
TEST_F(CollectAnyRangeTest, ThrowsFirstError) {
bool caughtException = false;
folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
bool throwError = true;
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<int>&&> {
co_yield [&]() -> folly::coro::Task<int> {
co_await folly::coro::co_reschedule_on_current_executor;
if (throwError) {
throw ErrorA{};
}
co_return 1;
}();
co_yield [&]() -> folly::coro::Task<int> {
if (throwError) {
throw ErrorB{};
}
co_return 2;
}();
co_yield [&]() -> folly::coro::Task<int> {
if (throwError) {
throw ErrorC{};
}
co_return 3;
}();
};
// Child tasks are started in-order.
// The first task will reschedule itself onto the executor.
// The second task will fail immediately and will be the first
// task to fail.
// Then the third and first tasks will fail.
// As the second task failed first we should see its exception
// propagate out of collectAny().
auto [index, result] =
co_await folly::coro::collectAnyRange(generateTasks());
CHECK_EQ(1, index);
try {
result.value();
CHECK(false);
} catch (const ErrorB&) {
caughtException = true;
}
}());
CHECK(caughtException);
}
TEST_F(CollectAnyRangeTest, CollectAnyCancelsSubtasksWhenASubtaskCompletes) {
using namespace std::chrono_literals;
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
auto start = std::chrono::steady_clock::now();
std::vector<folly::coro::Task<int>> tasks;
tasks.push_back([]() -> folly::coro::Task<int> {
co_await sleepThatShouldBeCancelled(10s);
co_return 42;
}());
tasks.push_back([]() -> folly::coro::Task<int> {
co_await sleepThatShouldBeCancelled(5s);
co_return 314;
}());
tasks.push_back([]() -> folly::coro::Task<int> {
co_await folly::coro::co_reschedule_on_current_executor;
throw ErrorA{};
}());
auto [index, result] =
co_await folly::coro::collectAnyRange(std::move(tasks));
CHECK_EQ(2, index);
try {
result.value();
CHECK(false);
} catch (const ErrorA&) {
}
auto end = std::chrono::steady_clock::now();
CHECK((end - start) < 1s);
}());
}
TEST_F(CollectAnyRangeTest, CollectAnyCancelsSubtasksWhenParentTaskCancelled) {
using namespace std::chrono_literals;
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
auto start = std::chrono::steady_clock::now();
folly::CancellationSource cancelSource;
try {
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<int>&&> {
co_yield [&]() -> folly::coro::Task<int> {
co_await sleepThatShouldBeCancelled(10s);
co_return 42;
}();
co_yield [&]() -> folly::coro::Task<int> {
co_await sleepThatShouldBeCancelled(5s);
co_return 314;
}();
co_yield [&]() -> folly::coro::Task<int> {
co_await folly::coro::co_reschedule_on_current_executor;
co_await folly::coro::co_reschedule_on_current_executor;
co_await folly::coro::co_reschedule_on_current_executor;
cancelSource.requestCancellation();
co_await sleepThatShouldBeCancelled(15s);
co_return 123;
}();
};
auto [index, result] = co_await folly::coro::co_withCancellation(
cancelSource.getToken(),
folly::coro::collectAnyRange(generateTasks()));
CHECK(false);
} catch (const folly::OperationCancelled&) {
auto end = std::chrono::steady_clock::now();
CHECK((end - start) < 1s);
}
}());
}
TEST_F(CollectAnyRangeTest, CancellationTokenRemainsActiveAfterReturn) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
folly::CancellationSource cancelSource;
folly::coro::AsyncScope scope;
folly::coro::Baton baton;
auto task = folly::coro::co_invoke([&]() -> folly::coro::Task<void> {
auto token = co_await folly::coro::co_current_cancellation_token;
auto ex = co_await folly::coro::co_current_executor;
scope.add(
folly::coro::co_withCancellation(
token, folly::coro::co_invoke([&]() -> folly::coro::Task<void> {
auto token =
co_await folly::coro::co_current_cancellation_token;
co_await baton;
EXPECT_TRUE(token.isCancellationRequested());
}))
.scheduleOn(ex));
});
std::vector<folly::coro::Task<void>> tasks;
tasks.push_back(std::move(task));
co_await folly::coro::co_withCancellation(
cancelSource.getToken(),
folly::coro::collectAnyRange(std::move(tasks)));
cancelSource.requestCancellation();
baton.post();
co_await scope.joinAsync();
}());
}
#endif
class CollectAnyNoDiscardRangeTest : public testing::Test {};
TEST_F(CollectAnyNoDiscardRangeTest, OneTask) {
auto value = [&]() -> folly::coro::Task<std::string> { co_return "hello"; };
auto throws = [&]() -> folly::coro::Task<std::string> {
co_yield folly::coro::co_error(ErrorA{});
};
folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
{
std::vector<folly::coro::Task<std::string>> tasks;
tasks.push_back(value());
auto result =
co_await folly::coro::collectAnyNoDiscardRange(std::move(tasks));
CHECK_EQ(result.size(), 1);
CHECK(result[0].hasValue());
CHECK_EQ("hello", result[0].value());
}
{
std::vector<folly::coro::Task<std::string>> tasks;
tasks.push_back(throws());
auto result =
co_await folly::coro::collectAnyNoDiscardRange(std::move(tasks));
CHECK_EQ(result.size(), 1);
CHECK(result[0].hasException<ErrorA>());
}
}());
}
TEST_F(CollectAnyNoDiscardRangeTest, MultipleTasksWithValues) {
std::atomic_size_t count{0};
// Busy wait until all threads have started before returning
auto busyWait = [](std::atomic_size_t& count,
size_t num) -> folly::coro::Task<void> {
count.fetch_add(1);
while (count.load() < num) {
// Need to yield because collectAnyNoDiscard() won't start the second and
// third coroutines until the first one gets to a suspend point
co_await folly::coro::co_reschedule_on_current_executor;
}
};
folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
std::vector<folly::coro::Task<void>> tasks;
tasks.push_back(busyWait(count, 3));
tasks.push_back(busyWait(count, 3));
tasks.push_back(busyWait(count, 3));
auto result =
co_await folly::coro::collectAnyNoDiscardRange(std::move(tasks));
CHECK_EQ(result.size(), 3);
CHECK(result[0].hasValue());
CHECK(result[1].hasValue());
CHECK(result[2].hasValue());
}());
}
TEST_F(CollectAnyNoDiscardRangeTest, OneVoidTask) {
bool completed = false;
folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
// Checks that the task actually runs and that 'void' results are
// promoted to folly::Unit when placed in a tuple.
auto makeTask = [&]() -> folly::coro::Task<void> {
completed = true;
co_return;
};
std::vector<folly::coro::Task<void>> tasks;
tasks.push_back(makeTask());
auto result =
co_await folly::coro::collectAnyNoDiscardRange(std::move(tasks));
EXPECT_EQ(result.size(), 1);
}());
CHECK(completed);
}
TEST_F(CollectAnyNoDiscardRangeTest, DoesntCompleteUntilAllTasksComplete) {
folly::coro::Baton baton1;
folly::coro::Baton baton2;
bool task1Started = false;
bool task2Started = false;
bool complete = false;
auto run = [&]() -> folly::coro::Task<void> {
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<int>&&> {
co_yield [&]() -> folly::coro::Task<int> {
task1Started = true;
co_await baton1;
co_return 42;
}();
co_yield [&]() -> folly::coro::Task<int> {
task2Started = true;
co_await baton2;
co_return 314;
}();
};
auto result =
co_await folly::coro::collectAnyNoDiscardRange(generateTasks());
complete = true;
CHECK_EQ(result.size(), 2);
CHECK(result[0].hasValue());
CHECK_EQ(result[0].value(), 42);
CHECK(result[1].hasValue());
CHECK_EQ(result[1].value(), 314);
};
folly::ManualExecutor executor;
auto future = run().scheduleOn(&executor).start();
CHECK(!task1Started);
CHECK(!task2Started);
executor.drain();
CHECK(task1Started);
CHECK(task2Started);
CHECK(!complete);
baton2.post();
executor.drain();
CHECK(!complete);
baton1.post();
executor.drain();
CHECK(complete);
CHECK(future.isReady());
}
TEST_F(CollectAnyNoDiscardRangeTest, ThrowsAllErrors) {
using namespace std::chrono_literals;
folly::coro::blockingWait([&]() -> folly::coro::Task<void> {
// Child tasks are started in-order.
// The first task will reschedule itself onto the executor.
// The second task will fail immediately and will be the first task to fail.
// Then the third and first tasks will fail.
// We should see all exceptions propagate out of collectAnyNoDiscard().
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<int>&&> {
co_yield [&]() -> folly::coro::Task<int> {
co_await folly::coro::co_reschedule_on_current_executor;
co_yield folly::coro::co_error(ErrorA{});
}();
co_yield [&]() -> folly::coro::Task<int> {
co_yield folly::coro::co_error(ErrorB{});
}();
co_yield [&]() -> folly::coro::Task<int> {
co_yield folly::coro::co_error(ErrorC{});
}();
};
auto result =
co_await folly::coro::collectAnyNoDiscardRange(generateTasks());
CHECK_EQ(result.size(), 3);
CHECK(result[0].hasException<ErrorA>());
CHECK(result[1].hasException<ErrorB>());
CHECK(result[2].hasException<ErrorC>());
}());
}
TEST_F(CollectAnyNoDiscardRangeTest, CancelSubtasksWhenASubtaskCompletes) {
using namespace std::chrono_literals;
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
auto start = std::chrono::steady_clock::now();
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<int>&&> {
co_yield []() -> folly::coro::Task<int> {
co_await folly::coro::sleep(10s);
co_return 42;
}();
co_yield []() -> folly::coro::Task<int> {
co_await folly::coro::sleep(5s);
co_return 314;
}();
co_yield []() -> folly::coro::Task<int> {
co_await folly::coro::co_reschedule_on_current_executor;
co_yield folly::coro::co_error(ErrorA{});
}();
};
auto result =
co_await folly::coro::collectAnyNoDiscardRange(generateTasks());
CHECK_EQ(result.size(), 3);
CHECK(result[0].hasException<folly::OperationCancelled>());
CHECK(result[1].hasException<folly::OperationCancelled>());
CHECK(result[2].hasException<ErrorA>());
auto end = std::chrono::steady_clock::now();
CHECK((end - start) < 1s);
}());
}
TEST_F(CollectAnyNoDiscardRangeTest, CancelSubtasksWhenParentTaskCancelled) {
using namespace std::chrono_literals;
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
auto start = std::chrono::steady_clock::now();
folly::CancellationSource cancelSource;
auto generateTasks =
[&]() -> folly::coro::Generator<folly::coro::Task<int>&&> {
co_yield [&]() -> folly::coro::Task<int> {
co_await folly::coro::sleep(10s);
co_return 42;
}();
co_yield [&]() -> folly::coro::Task<int> {
co_await folly::coro::sleep(5s);
co_return 314;
}();
co_yield [&]() -> folly::coro::Task<int> {
co_await folly::coro::co_reschedule_on_current_executor;
co_await folly::coro::co_reschedule_on_current_executor;
co_await folly::coro::co_reschedule_on_current_executor;
cancelSource.requestCancellation();
co_await folly::coro::sleep(15s);
co_return 123;
}();
};
auto result = co_await folly::coro::co_withCancellation(
cancelSource.getToken(),
folly::coro::collectAnyNoDiscardRange(generateTasks()));
auto end = std::chrono::steady_clock::now();
CHECK((end - start) < 1s);
CHECK_EQ(result.size(), 3);
CHECK(result[0].hasException<folly::OperationCancelled>());
CHECK(result[1].hasException<folly::OperationCancelled>());
CHECK(result[2].hasException<folly::OperationCancelled>());
}());
}
TEST_F(
CollectAnyNoDiscardRangeTest, CancellationTokenRemainsActiveAfterReturn) {
folly::coro::blockingWait([]() -> folly::coro::Task<void> {
folly::CancellationSource cancelSource;
folly::coro::AsyncScope scope;
folly::coro::Baton baton;
std::vector<folly::coro::Task<void>> tasks;
tasks.push_back(folly::coro::co_invoke([&]() -> folly::coro::Task<void> {
auto token = co_await folly::coro::co_current_cancellation_token;
auto ex = co_await folly::coro::co_current_executor;
scope.add(
folly::coro::co_withCancellation(
token, folly::coro::co_invoke([&]() -> folly::coro::Task<void> {
auto token =
co_await folly::coro::co_current_cancellation_token;
co_await baton;
EXPECT_TRUE(token.isCancellationRequested());
}))
.scheduleOn(ex));
}));
co_await folly::coro::co_withCancellation(
cancelSource.getToken(),
folly::coro::collectAnyNoDiscardRange(std::move(tasks)));
cancelSource.requestCancellation();
baton.post();
co_await scope.joinAsync();
}());
}
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