1
0
Fork 0
mirror of https://gitlab.com/niansa/libcrosscoro.git synced 2025-03-06 20:53:32 +01:00
libcrosscoro/inc/coro/concepts/awaitable.hpp
Josh Baldwin e9b225e42f
io_scheduler inline support (#79)
* io_scheduler inline support

* add debug info for io_scheduler size issue

* move poll info into its own file

* cleanup for feature

* Fix valgrind introduced use after free with inline processing

Running the coroutines inline with event processing caused
a use after free bug with valgrind detected in the inline
tcp server/client benchmark code.  Basically if an event
and a timeout occured in the same time period because the
inline processing would resume _inline_ with the event or the
timeout -- if the timeout and event occured in the same epoll_wait()
function call then the second one's coroutine stackframe would
already be destroyed upon resuming it so the poll_info->processed
check would be reading already free'ed memory.

The solution to this was to introduce a vector of coroutine handles
which are appended into on each epoll_wait() iteration of events
and timeouts, and only then after the events and timeouts are
deduplicated are the coroutine handles resumed.

This new vector has elided a malloc in the timeout function, but
there is still a malloc to extract the poll infos from the timeout
multimap data structure.  The vector is also on the class member
list and is only ever cleared, it is possible with a monster set
of timeouts that this vector could grow extremely large, but
I think that is worth the price of not re-allocating it.
2021-04-11 15:07:01 -06:00

75 lines
2.1 KiB
C++

#pragma once
#include <concepts>
#include <coroutine>
#include <type_traits>
#include <utility>
namespace coro::concepts
{
/**
* This concept declares a type that is required to meet the c++20 coroutine operator co_await()
* retun type. It requires the following three member functions:
* await_ready() -> bool
* await_suspend(std::coroutine_handle<>) -> void|bool|std::coroutine_handle<>
* await_resume() -> decltype(auto)
* Where the return type on await_resume is the requested return of the awaitable.
*/
// clang-format off
template<typename type>
concept awaiter = requires(type t, std::coroutine_handle<> c)
{
{ t.await_ready() } -> std::same_as<bool>;
std::same_as<decltype(t.await_suspend(c)), void> ||
std::same_as<decltype(t.await_suspend(c)), bool> ||
std::same_as<decltype(t.await_suspend(c)), std::coroutine_handle<>>;
{ t.await_resume() };
};
/**
* This concept declares a type that can be operator co_await()'ed and returns an awaiter_type.
*/
template<typename type>
concept awaitable = requires(type t)
{
// operator co_await()
{ t.operator co_await() } -> awaiter;
};
template<typename type>
concept awaiter_void = requires(type t, std::coroutine_handle<> c)
{
{ t.await_ready() } -> std::same_as<bool>;
std::same_as<decltype(t.await_suspend(c)), void> ||
std::same_as<decltype(t.await_suspend(c)), bool> ||
std::same_as<decltype(t.await_suspend(c)), std::coroutine_handle<>>;
{t.await_resume()} -> std::same_as<void>;
};
template<typename type>
concept awaitable_void = requires(type t)
{
// operator co_await()
{ t.operator co_await() } -> awaiter_void;
};
template<awaitable awaitable, typename = void>
struct awaitable_traits
{
};
template<awaitable awaitable>
static auto get_awaiter(awaitable&& value)
{
return std::forward<awaitable>(value).operator co_await();
}
template<awaitable awaitable>
struct awaitable_traits<awaitable>
{
using awaiter_type = decltype(get_awaiter(std::declval<awaitable>()));
using awaiter_return_type = decltype(std::declval<awaiter_type>().await_resume());
};
// clang-format on
} // namespace coro::concepts