Luna/libos/src/EventLoop.cpp
apio 0824ba7e23
libos: Add timers to event loops
Only second precision for now, as alarm() is used to control the timers. Hopefully setitimer() or timer_create() can be added to the kernel soon to benefit from more precision.
2023-10-09 22:00:15 +02:00

168 lines
4.3 KiB
C++

/**
* @file EventLoop.cpp
* @author apio (cloudapio.eu)
* @brief Base class for event-driven applications.
*
* @copyright Copyright (c) 2023, the Luna authors.
*
*/
#include <errno.h>
#include <os/EventLoop.h>
#include <os/File.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
static os::EventLoop* s_the = nullptr;
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<void> 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<void> 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<void> EventLoop::register_timer(unsigned int seconds, Action&& callback)
{
long target = time(NULL) + seconds;
auto* timer = TRY(make<Timer>());
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(seconds);
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 - time(NULL))); }
timer->action();
delete timer;
}
}
}