mirror of
https://gitlab.com/niansa/libcrosscoro.git
synced 2025-03-06 20:53:32 +01:00
* 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.
74 lines
3 KiB
C++
74 lines
3 KiB
C++
#pragma once
|
|
|
|
#include "coro/fd.hpp"
|
|
#include "coro/poll.hpp"
|
|
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <coroutine>
|
|
#include <map>
|
|
#include <optional>
|
|
|
|
namespace coro::detail
|
|
{
|
|
/**
|
|
* Poll Info encapsulates everything about a poll operation for the event as well as its paired
|
|
* timeout. This is important since coroutines that are waiting on an event or timeout do not
|
|
* immediately execute, they are re-scheduled onto the thread pool, so its possible its pair
|
|
* event or timeout also triggers while the coroutine is still waiting to resume. This means that
|
|
* the first one to happen, the event itself or its timeout, needs to disable the other pair item
|
|
* prior to resuming the coroutine.
|
|
*
|
|
* Finally, its also important to note that the event and its paired timeout could happen during
|
|
* the same epoll_wait and possibly trigger the coroutine to start twice. Only one can win, so the
|
|
* first one processed sets m_processed to true and any subsequent events in the same epoll batch
|
|
* are effectively discarded.
|
|
*/
|
|
struct poll_info
|
|
{
|
|
using clock = std::chrono::steady_clock;
|
|
using time_point = clock::time_point;
|
|
using timed_events = std::multimap<time_point, detail::poll_info*>;
|
|
|
|
poll_info() = default;
|
|
~poll_info() = default;
|
|
|
|
poll_info(const poll_info&) = delete;
|
|
poll_info(poll_info&&) = delete;
|
|
auto operator=(const poll_info&) -> poll_info& = delete;
|
|
auto operator=(poll_info&&) -> poll_info& = delete;
|
|
|
|
struct poll_awaiter
|
|
{
|
|
explicit poll_awaiter(poll_info& pi) noexcept : m_pi(pi) {}
|
|
|
|
auto await_ready() const noexcept -> bool { return false; }
|
|
auto await_suspend(std::coroutine_handle<> awaiting_coroutine) noexcept -> void
|
|
{
|
|
m_pi.m_awaiting_coroutine = awaiting_coroutine;
|
|
std::atomic_thread_fence(std::memory_order::release);
|
|
}
|
|
auto await_resume() noexcept -> coro::poll_status { return m_pi.m_poll_status; }
|
|
|
|
poll_info& m_pi;
|
|
};
|
|
|
|
auto operator co_await() noexcept -> poll_awaiter { return poll_awaiter{*this}; }
|
|
|
|
/// The file descriptor being polled on. This is needed so that if the timeout occurs first then
|
|
/// the event loop can immediately disable the event within epoll.
|
|
fd_t m_fd{-1};
|
|
/// The timeout's position in the timeout map. A poll() with no timeout or yield() this is empty.
|
|
/// This is needed so that if the event occurs first then the event loop can immediately disable
|
|
/// the timeout within epoll.
|
|
std::optional<timed_events::iterator> m_timer_pos{std::nullopt};
|
|
/// The awaiting coroutine for this poll info to resume upon event or timeout.
|
|
std::coroutine_handle<> m_awaiting_coroutine;
|
|
/// The status of the poll operation.
|
|
coro::poll_status m_poll_status{coro::poll_status::error};
|
|
/// Did the timeout and event trigger at the same time on the same epoll_wait call?
|
|
/// Once this is set to true all future events on this poll info are null and void.
|
|
bool m_processed{false};
|
|
};
|
|
|
|
} // namespace coro::detail
|