1
0
Fork 0
mirror of https://gitlab.com/niansa/libcrosscoro.git synced 2025-03-06 20:53:32 +01:00

coro::when_all(tuple) use after free fix (#56)

This commit is contained in:
Josh Baldwin 2021-02-15 17:18:20 -07:00 committed by GitHub
parent e1e52b1400
commit 017a4e2621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 66 additions and 5 deletions

View file

@ -438,8 +438,26 @@ private:
template<
concepts::awaitable awaitable,
typename return_type = concepts::awaitable_traits<awaitable&&>::awaiter_return_type>
static auto make_when_all_task(awaitable&& a) -> when_all_task<return_type>
static auto make_when_all_task(awaitable& a) -> when_all_task<return_type>
{
// Use this version if the awaitable can be taken as a reference (non-owning).
if constexpr (std::is_void_v<return_type>)
{
co_await static_cast<awaitable&&>(a);
co_return;
}
else
{
co_yield co_await static_cast<awaitable&&>(a);
}
}
template<
concepts::awaitable awaitable,
typename return_type = concepts::awaitable_traits<awaitable&&>::awaiter_return_type>
static auto make_when_all_task_owned(awaitable a) -> when_all_task<return_type>
{
// Use this version if the awaitable needs to be owned by this task.
if constexpr (std::is_void_v<return_type>)
{
co_await static_cast<awaitable&&>(a);
@ -454,11 +472,11 @@ static auto make_when_all_task(awaitable&& a) -> when_all_task<return_type>
} // namespace detail
template<concepts::awaitable... awaitables_type>
[[nodiscard]] auto when_all(awaitables_type&&... awaitables)
[[nodiscard]] auto when_all(awaitables_type... awaitables)
{
return detail::when_all_ready_awaitable<std::tuple<
detail::when_all_task<typename concepts::awaitable_traits<awaitables_type>::awaiter_return_type>...>>(
std::make_tuple(detail::make_when_all_task(std::forward<awaitables_type>(awaitables))...));
std::make_tuple(detail::make_when_all_task_owned(std::move(awaitables))...));
}
template<
@ -473,7 +491,7 @@ template<
for (auto& a : awaitables)
{
tasks.emplace_back(detail::make_when_all_task(std::move(a)));
tasks.emplace_back(detail::make_when_all_task(a));
}
return detail::when_all_ready_awaitable(std::move(tasks));

View file

@ -83,6 +83,35 @@ TEST_CASE("benchmark counter func coro::sync_wait(coro::when_all(awaitable)) x10
REQUIRE(counter == iterations);
}
TEST_CASE("benchmark counter func coro::sync_wait(coro::when_all(vector<awaitable>)) x10", "[benchmark]")
{
constexpr std::size_t iterations = default_iterations;
uint64_t counter{0};
auto f = []() -> coro::task<uint64_t> { co_return 1; };
auto start = sc::now();
for (std::size_t i = 0; i < iterations; i += 10)
{
std::vector<coro::task<uint64_t>> tasks{};
tasks.reserve(10);
for (size_t j = 0; j < 10; ++j)
{
tasks.emplace_back(f());
}
auto results = coro::sync_wait(coro::when_all(tasks));
for (const auto& r : results)
{
counter += r.return_value();
}
}
print_stats("benchmark counter func coro::sync_wait(coro::when_all(awaitable))", iterations, start, sc::now());
REQUIRE(counter == iterations);
}
TEST_CASE("benchmark thread_pool{1} counter task", "[benchmark]")
{
constexpr std::size_t iterations = default_iterations;
@ -107,6 +136,10 @@ TEST_CASE("benchmark thread_pool{1} counter task", "[benchmark]")
tasks.back().resume();
}
// This will fail in valgrind since it runs in a single 'thread', and thus is shutsdown prior
// to any coroutine actually getting properly scheduled onto the background thread pool.
// Inject a sleep here so it forces a thread context switch within valgrind.
std::this_thread::sleep_for(std::chrono::milliseconds{10});
tp.shutdown();
print_stats("benchmark thread_pool{1} counter task", iterations, start, sc::now());
@ -138,6 +171,10 @@ TEST_CASE("benchmark thread_pool{2} counter task", "[benchmark]")
tasks.back().resume();
}
// This will fail in valgrind since it runs in a single 'thread', and thus is shutsdown prior
// to any coroutine actually getting properly scheduled onto the background thread pool.
// Inject a sleep here so it forces a thread context switch within valgrind.
std::this_thread::sleep_for(std::chrono::milliseconds{10});
tp.shutdown();
print_stats("benchmark thread_pool{2} counter task", iterations, start, sc::now());
@ -255,8 +292,14 @@ TEST_CASE("benchmark counter task scheduler await event from another coroutine",
auto stop = sc::now();
print_stats("benchmark counter task scheduler await event from another coroutine", ops, start, stop);
REQUIRE(s.empty());
REQUIRE(counter == iterations);
// valgrind workaround
while (!s.empty())
{
std::this_thread::sleep_for(std::chrono::milliseconds{1});
}
REQUIRE(s.empty());
}
TEST_CASE("benchmark tcp_server echo server", "[benchmark]")