2025-09-29 12:24:58 -04:00
|
|
|
#include"Ev/coroutine.hpp"
|
|
|
|
|
#include<cassert>
|
2025-12-16 10:41:06 -08:00
|
|
|
#include<cstdio>
|
|
|
|
|
#include<cstdlib>
|
|
|
|
|
#include<functional>
|
|
|
|
|
#include<thread>
|
2025-09-29 12:24:58 -04:00
|
|
|
#include<ev.h>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
2025-12-16 10:31:22 -08:00
|
|
|
/* Take advantage of the fact that `Ev::Io` is
|
2025-09-29 12:24:58 -04:00
|
|
|
fully intended to be used only in the main thread.
|
|
|
|
|
*/
|
|
|
|
|
auto cleaning_list = (Ev::coroutine::ToBeCleaned*)(nullptr);
|
|
|
|
|
|
2025-12-12 21:29:51 -08:00
|
|
|
/* To avoid allocating in schedule_for_cleaning, also
|
2025-09-29 12:24:58 -04:00
|
|
|
statically-allocate the idler object.
|
|
|
|
|
*/
|
|
|
|
|
auto cleaning_idle = ev_idle();
|
|
|
|
|
|
|
|
|
|
void cleaning_handler(EV_P_ ev_idle* raw_idler, int) {
|
|
|
|
|
assert(raw_idler == &cleaning_idle);
|
|
|
|
|
/* Tell libev to stop the idling. */
|
|
|
|
|
ev_idle_stop(EV_A_ &cleaning_idle);
|
|
|
|
|
/* And clean it up, which should reset the
|
|
|
|
|
cleaning list to empty.
|
|
|
|
|
*/
|
|
|
|
|
Ev::coroutine::do_cleaning_as_scheduled();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace Ev::coroutine {
|
|
|
|
|
|
2025-12-16 10:41:06 -08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
|
namespace detail {
|
|
|
|
|
|
|
|
|
|
auto const main_thread_id = std::this_thread::get_id();
|
|
|
|
|
|
|
|
|
|
[[noreturn]]
|
|
|
|
|
void assert_fail_off_main_thread(char const* where) noexcept {
|
|
|
|
|
auto const main_tid = std::hash<std::thread::id>()(main_thread_id);
|
|
|
|
|
auto const curr_tid = std::hash<std::thread::id>()(std::this_thread::get_id());
|
|
|
|
|
std::fprintf(stderr,
|
|
|
|
|
"%s called off main thread (main tid hash=%zu, current tid hash=%zu).\n"
|
|
|
|
|
"This is unsafe: it mutates coroutine cleanup state and uses libev watchers without synchronization.\n",
|
|
|
|
|
where, main_tid, curr_tid);
|
|
|
|
|
std::abort();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void assert_main_thread(char const* where) noexcept {
|
|
|
|
|
if (std::this_thread::get_id() != main_thread_id) {
|
|
|
|
|
assert_fail_off_main_thread(where);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-09-29 12:24:58 -04:00
|
|
|
void schedule_for_cleaning(ToBeCleaned* obj) noexcept {
|
2025-12-16 10:41:06 -08:00
|
|
|
#ifndef NDEBUG
|
|
|
|
|
detail::assert_main_thread("Ev::coroutine::schedule_for_cleaning");
|
|
|
|
|
#endif
|
2025-09-29 12:24:58 -04:00
|
|
|
auto need_to_schedule = (cleaning_list == nullptr);
|
|
|
|
|
obj->next = cleaning_list;
|
|
|
|
|
cleaning_list = obj;
|
|
|
|
|
if (!need_to_schedule) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
/* Should not fail. Dunno if libev has to allocate
|
|
|
|
|
here but at least these are C libraries and should
|
|
|
|
|
not *throw* at least (^^;).
|
|
|
|
|
*/
|
|
|
|
|
ev_idle_init(&cleaning_idle, &cleaning_handler);
|
|
|
|
|
ev_idle_start(EV_DEFAULT_ &cleaning_idle);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-12 15:01:48 -08:00
|
|
|
void do_cleaning_as_scheduled() noexcept {
|
2025-09-29 12:24:58 -04:00
|
|
|
while (cleaning_list) {
|
|
|
|
|
auto head = cleaning_list;
|
|
|
|
|
auto next = head->next;
|
|
|
|
|
head->next = nullptr;
|
|
|
|
|
cleaning_list = next;
|
|
|
|
|
/* Make a best-effort at cleaning this up. */
|
|
|
|
|
try {
|
|
|
|
|
head->clean();
|
|
|
|
|
} catch (...) { }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|