/** * @file EventLoop.cpp * @author apio (cloudapio.eu) * @brief Base class for event-driven applications. * * @copyright Copyright (c) 2023, the Luna authors. * */ #include #include #include #include #include #include #include #include static os::EventLoop* s_the = nullptr; static void alarm_milliseconds(long target) { struct itimerval itimer; memset(&itimer.it_interval, 0, sizeof(itimer.it_interval)); itimer.it_value = { .tv_sec = target / 1000, .tv_usec = (target % 1000) * 1000 }; setitimer(ITIMER_REAL, &itimer, nullptr); } static long get_monotonic_time_in_milliseconds() { struct timespec time; clock_gettime(CLOCK_MONOTONIC, &time); return time.tv_sec * 1000 + time.tv_nsec / 1'000'000; } namespace os { EventLoop::EventLoop() { s_the = this; // Set up synchronous signal handling. check(pipe(m_signal_pipe) == 0); m_pollfds.try_append({ .fd = m_signal_pipe[0], .events = POLLIN, .revents = 0 }).release_value(); register_signal_handler(SIGTERM, EventLoop::handle_quit_signal).release_value(); register_signal_handler(SIGINT, EventLoop::handle_quit_signal).release_value(); register_signal_handler(SIGALRM, EventLoop::handle_timer_signal).release_value(); } EventLoop::~EventLoop() { s_the = nullptr; } EventLoop& EventLoop::the() { check(s_the); return *s_the; } Result EventLoop::register_fd_listener(int fd, void (*listener)(int, int)) { TRY(m_fd_listeners.try_set(fd, listener)); TRY(m_pollfds.try_append({ .fd = fd, .events = POLLIN, .revents = 0 })); return {}; } Result EventLoop::register_signal_handler(int sig, void (*handler)(int)) { struct sigaction sa; sa.sa_handler = EventLoop::handle_signal; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); check(sigaction(sig, &sa, nullptr) == 0); TRY(m_signal_handlers.try_set(sig, handler)); return {}; } Result EventLoop::register_timer(long milliseconds, Action&& callback) { long target = get_monotonic_time_in_milliseconds() + milliseconds; auto* timer = TRY(make()); timer->target = target; timer->action = move(callback); Timer* next = nullptr; for (auto* t : m_timer_queue) { if (target < t->target) { next = t; break; } } if (m_timer_queue.first().value_or(nullptr) == next) { alarm_milliseconds(milliseconds); m_timer_queue.prepend(timer); } else if (next == nullptr) { m_timer_queue.append(timer); } else { auto previous = m_timer_queue.previous(next).value(); m_timer_queue.add_after(previous, timer); } return {}; } int EventLoop::run() { while (!m_should_quit) { for (auto& pfd : m_pollfds) { pfd.revents = 0; } int rc = poll(m_pollfds.data(), m_pollfds.size(), 1000); if (!rc) continue; if (rc < 0 && errno != EINTR) { os::println("poll: error: %s", strerror(errno)); return 1; } if (m_pollfds[0].revents & POLLIN) { int sig; read(m_signal_pipe[0], &sig, sizeof(int)); auto handler = m_signal_handlers.try_get(sig); if (handler.has_value()) { handler.value()(sig); } } for (usize i = 0; i < m_fd_listeners.size(); i++) { auto& pfd = m_pollfds[i + 1]; if (pfd.revents) { auto handler = m_fd_listeners.try_get(pfd.fd); if (handler.has_value()) handler.value()(pfd.fd, pfd.revents); } } } return m_quit_status; } void EventLoop::quit(int status) { m_quit_status = status; m_should_quit = true; } void EventLoop::handle_signal(int sig) { write(the().m_signal_pipe[1], &sig, sizeof(int)); } void EventLoop::handle_quit_signal(int sig) { os::println("EventLoop: quit requested by signal %d", sig); the().quit(sig); } void EventLoop::handle_timer_signal(int) { auto& queue = the().m_timer_queue; if (queue.count()) { auto timer = queue.remove(queue.expect_first()); auto first = queue.first(); if (first.has_value()) { alarm((unsigned int)(first.value()->target - get_monotonic_time_in_milliseconds())); } timer->action(); delete timer; } } }