From a9c339939a17925421aa3587a7d603a4ccd3033c Mon Sep 17 00:00:00 2001 From: apio Date: Fri, 5 Jan 2024 22:16:50 +0100 Subject: [PATCH] libos: Move timer handling to a separate class and use POSIX timers --- libos/CMakeLists.txt | 1 + libos/include/os/EventLoop.h | 21 ++------- libos/include/os/Timer.h | 39 ++++++++++++++++ libos/src/EventLoop.cpp | 67 ++-------------------------- libos/src/Timer.cpp | 86 ++++++++++++++++++++++++++++++++++++ 5 files changed, 133 insertions(+), 81 deletions(-) create mode 100644 libos/include/os/Timer.h create mode 100644 libos/src/Timer.cpp diff --git a/libos/CMakeLists.txt b/libos/CMakeLists.txt index bf069a59..3ad1f5d3 100644 --- a/libos/CMakeLists.txt +++ b/libos/CMakeLists.txt @@ -19,6 +19,7 @@ set(SOURCES src/LocalClient.cpp src/IPC.cpp src/SharedMemory.cpp + src/Timer.cpp ) add_library(os ${SOURCES}) diff --git a/libos/include/os/EventLoop.h b/libos/include/os/EventLoop.h index 6eb6a9e5..9bef9350 100644 --- a/libos/include/os/EventLoop.h +++ b/libos/include/os/EventLoop.h @@ -3,15 +3,15 @@ * @author apio (cloudapio.eu) * @brief Base class for event-driven applications. * - * @copyright Copyright (c) 2023, the Luna authors. + * @copyright Copyright (c) 2023-2024, the Luna authors. * */ #pragma once #include -#include #include #include +#include #include namespace os @@ -61,15 +61,6 @@ namespace os */ Result register_signal_handler(int sig, void (*handler)(int)); - /** - * @brief Register a function to be called in a certain amount of time. - * - * @param milliseconds The number of milliseconds to wait before invoking the function. - * @param callback The function to call when the time is up. - * @return Result Whether the operation succeeded. - */ - Result register_timer(long milliseconds, Action&& callback); - /** * @brief Run the event loop until it is asked to quit. * @@ -98,12 +89,8 @@ namespace os int m_signal_pipe[2]; - struct Timer final : public LinkedListNode - { - long target; - Action action; - }; + LinkedList m_timer_list; - LinkedList m_timer_queue; + friend class Timer; }; } diff --git a/libos/include/os/Timer.h b/libos/include/os/Timer.h new file mode 100644 index 00000000..eeb503af --- /dev/null +++ b/libos/include/os/Timer.h @@ -0,0 +1,39 @@ +/** + * @file Timer.h + * @author apio (cloudapio.eu) + * @brief Millisecond-precision timer. + * + * @copyright Copyright (c) 2024, the Luna authors. + * + */ + +#pragma once +#include +#include +#include +#include + +namespace os +{ + class EventLoop; + class Timer : LinkedListNode + { + public: + static Result> create_singleshot(unsigned int milliseconds, Action&& action); + static Result> create_repeating(unsigned int milliseconds, Action&& action); + + ~Timer(); + + private: + timer_t m_timerid { -1 }; + bool m_repeating; + struct timespec m_interval; + Action m_action; + + void check_if_should_invoke_action(); + + Timer() = default; + + friend class EventLoop; + }; +} diff --git a/libos/src/EventLoop.cpp b/libos/src/EventLoop.cpp index c699c1c6..acc042fc 100644 --- a/libos/src/EventLoop.cpp +++ b/libos/src/EventLoop.cpp @@ -3,7 +3,7 @@ * @author apio (cloudapio.eu) * @brief Base class for event-driven applications. * - * @copyright Copyright (c) 2023, the Luna authors. + * @copyright Copyright (c) 2023-2024, the Luna authors. * */ @@ -18,21 +18,6 @@ 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() @@ -78,40 +63,6 @@ namespace os 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) @@ -168,19 +119,7 @@ namespace os 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; - } + auto& queue = the().m_timer_list; + queue.delayed_for_each([](Timer* t) { t->check_if_should_invoke_action(); }); } } diff --git a/libos/src/Timer.cpp b/libos/src/Timer.cpp new file mode 100644 index 00000000..4a958245 --- /dev/null +++ b/libos/src/Timer.cpp @@ -0,0 +1,86 @@ +/** + * @file Timer.cpp + * @author apio (cloudapio.eu) + * @brief Millisecond-precision timer. + * + * @copyright Copyright (c) 2024, the Luna authors. + * + */ + +#include +#include +#include + +namespace os +{ + Result> Timer::create_singleshot(unsigned int milliseconds, Action&& action) + { + auto timer = TRY(adopt_owned_if_nonnull(new (std::nothrow) Timer())); + + int status = timer_create(CLOCK_REALTIME, nullptr, &timer->m_timerid); + if (status < 0) return err(errno); + + struct timespec time = { .tv_sec = milliseconds / 1000, .tv_nsec = (milliseconds % 1000) * 1'000'000 }; + struct itimerspec itimer = { .it_interval = { .tv_sec = 0, .tv_nsec = 0 }, .it_value = time }; + + timer_settime(timer->m_timerid, 0, &itimer, nullptr); + + timer->m_repeating = false; + timer->m_action = move(action); + + EventLoop::the().m_timer_list.append(timer.ptr()); + + return timer; + } + + Result> Timer::create_repeating(unsigned int milliseconds, Action&& action) + { + auto timer = TRY(adopt_owned_if_nonnull(new (std::nothrow) Timer())); + + int status = timer_create(CLOCK_REALTIME, nullptr, &timer->m_timerid); + if (status < 0) return err(errno); + + struct timespec time = { .tv_sec = milliseconds / 1000, .tv_nsec = (milliseconds % 1000) * 1'000'000 }; + struct itimerspec itimer = { .it_interval = { .tv_sec = 0, .tv_nsec = 0 }, .it_value = time }; + + timer_settime(timer->m_timerid, 0, &itimer, nullptr); + + timer->m_repeating = true; + timer->m_interval = time; + timer->m_action = move(action); + + EventLoop::the().m_timer_list.append(timer.ptr()); + + return timer; + } + + void Timer::check_if_should_invoke_action() + { + struct itimerspec remaining; + if (timer_gettime(m_timerid, &remaining) < 0) return; + + if (!remaining.it_value.tv_sec && !remaining.it_value.tv_nsec) + { + // Timer expired! + m_action(); + + if (!m_repeating) + { + timer_delete(m_timerid); + EventLoop::the().m_timer_list.remove(this); + m_timerid = -1; + } + else + { + struct itimerspec itimer = { .it_interval = { .tv_sec = 0, .tv_nsec = 0 }, .it_value = m_interval }; + + timer_settime(m_timerid, 0, &itimer, nullptr); + } + } + } + + Timer::~Timer() + { + if (m_timerid >= 0) timer_delete(m_timerid); + } +}