From 1cd355a8e805b12475c3cf4ba53e82d48f630e86 Mon Sep 17 00:00:00 2001 From: apio Date: Thu, 18 Jan 2024 20:52:36 +0100 Subject: [PATCH] libos: Add Function and use that to make EventLoop callbacks more versatile --- libos/include/os/Action.h | 134 +++++++++++++++++++++++++++++++---- libos/include/os/EventLoop.h | 8 +-- libos/src/EventLoop.cpp | 13 ++-- 3 files changed, 132 insertions(+), 23 deletions(-) diff --git a/libos/include/os/Action.h b/libos/include/os/Action.h index 530ec178..4b09d8f2 100644 --- a/libos/include/os/Action.h +++ b/libos/include/os/Action.h @@ -9,11 +9,12 @@ #pragma once #include +#include namespace os { /** - * @brief Wrapper for callable objects. + * @brief Wrapper for callable objects with no arguments. * * Certain callable types such as capture lambdas don't have a named type, thus they can't be passed to functions as * arguments. The Action type acts as a callable wrapper for such functions, allowing them to be passed as arguments @@ -40,7 +41,16 @@ namespace os */ Action(Action&& other) : m_action(other.m_action) { - other.m_action = nullptr; + other.m_action = {}; + } + + /** + * @brief Construct a new Action object, copying it from another one. + * + * @param other The old Action object to copy from. + */ + Action(const Action& other) : m_action(other.m_action) + { } /** @@ -52,7 +62,8 @@ namespace os */ template Action(T&& function) { - m_action = new (std::nothrow) ActionImpl(move(function)); + m_action = + adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl(move(function))).release_value(); } /** @@ -65,8 +76,8 @@ namespace os */ template Action& operator=(T&& function) { - if (m_action) delete m_action; - m_action = new (std::nothrow) ActionImpl(move(function)); + m_action = + adopt_shared_if_nonnull((ActionBase*)new (std::nothrow) ActionImpl(move(function))).release_value(); return *this; } @@ -79,13 +90,8 @@ namespace os return m_action->call(); } - ~Action() - { - if (m_action) delete m_action; - } - private: - class ActionBase + class ActionBase : public Shareable { public: virtual void call() = 0; @@ -108,6 +114,110 @@ namespace os T m_function; }; - ActionBase* m_action; + SharedPtr m_action; + }; + + /** + * @brief Wrapper for callable objects. + * + * Certain callable types such as capture lambdas don't have a named type, thus they can't be passed to functions as + * arguments. The Function type acts as a callable wrapper for such functions, allowing them to be passed as + * arguments to any function. + */ + template class Function + { + public: + /** + * @brief Construct a new empty Function object. + * + * Since it is not wrapping any callable object, this object is invalid and attempting to invoke its + * (nonexistent) callable object will result in a crash at runtime. + */ + Function() + { + m_action = nullptr; + } + + /** + * @brief Construct a new Function object, moving it from another one. + * + * @param other The old Function object to move from. This object will become invalid. + */ + Function(Function&& other) : m_action(other.m_action) + { + other.m_action = {}; + } + + /** + * @brief Construct a new Function object, copying it from another one. + * + * @param other The old Function object to copy from. + */ + Function(const Function& other) : m_action(other.m_action) + { + } + + /** + * @brief Construct a new Function object from a callable object. + * + * @tparam T The type of the callable object. Since this can be guessed by template deduction, the object + * doesn't actually need a type name. + * @param function The callable object to wrap. + */ + template Function(T&& function) + { + m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl(move(function))) + .release_value(); + } + + /** + * @brief Assign a new callable object to this Function. + * + * @tparam T The type of the callable object. Since this can be guessed by template deduction, the object + * doesn't actually need a type name. + * @param function The callable object to wrap. + * @return Action& A reference to this object, as required by operator=(). + */ + template Function& operator=(T&& function) + { + m_action = adopt_shared_if_nonnull((FunctionBase*)new (std::nothrow) FunctionImpl(move(function))) + .release_value(); + return *this; + } + + /** + * @brief Call the underlying object. + */ + void operator()(Args... args) + { + expect(m_action, "os::Function called with no underlying callable object"); + return m_action->call(args...); + } + + private: + class FunctionBase : public Shareable + { + public: + virtual void call(Args... args) = 0; + virtual ~FunctionBase() = default; + }; + + template class FunctionImpl final : public FunctionBase + { + public: + FunctionImpl(T&& function) : m_function(move(function)) + { + } + + void call(Args... args) override + { + return m_function(args...); + } + + private: + T m_function; + }; + + SharedPtr m_action; }; } diff --git a/libos/include/os/EventLoop.h b/libos/include/os/EventLoop.h index 9bef9350..d8ddcaa8 100644 --- a/libos/include/os/EventLoop.h +++ b/libos/include/os/EventLoop.h @@ -47,7 +47,7 @@ namespace os * is the file descriptor registered, and the second argument is the type of event (POLLIN or POLLHUP) * @return Result Whether the operation succeeded. */ - Result register_fd_listener(int fd, void (*listener)(int, int)); + Result register_fd_listener(int fd, Function listener); /** * @brief Register a new POSIX signal handler. @@ -59,7 +59,7 @@ namespace os * @param handler The function to call when this signal is caught. * @return Result Whether the operation succeeded. */ - Result register_signal_handler(int sig, void (*handler)(int)); + Result register_signal_handler(int sig, Function handler); /** * @brief Run the event loop until it is asked to quit. @@ -76,8 +76,8 @@ namespace os void quit(int status = 0); private: - HashMap m_fd_listeners; - HashMap m_signal_handlers; + HashMap> m_fd_listeners; + HashMap> m_signal_handlers; Vector m_pollfds; static void handle_signal(int sig); diff --git a/libos/src/EventLoop.cpp b/libos/src/EventLoop.cpp index d7af3266..2cfb9829 100644 --- a/libos/src/EventLoop.cpp +++ b/libos/src/EventLoop.cpp @@ -44,15 +44,14 @@ namespace os return *s_the; } - // FIXME: Support lambdas (by using os::Action). This means that os::Action needs variable parameter support. - Result EventLoop::register_fd_listener(int fd, void (*listener)(int, int)) + Result EventLoop::register_fd_listener(int fd, Function listener) { - TRY(m_fd_listeners.try_set(fd, listener)); + TRY(m_fd_listeners.try_set(fd, move(listener))); TRY(m_pollfds.try_append({ .fd = fd, .events = POLLIN, .revents = 0 })); return {}; } - Result EventLoop::register_signal_handler(int sig, void (*handler)(int)) + Result EventLoop::register_signal_handler(int sig, Function handler) { struct sigaction sa; sa.sa_handler = EventLoop::handle_signal; @@ -60,7 +59,7 @@ namespace os sigemptyset(&sa.sa_mask); check(sigaction(sig, &sa, nullptr) == 0); - TRY(m_signal_handlers.try_set(sig, handler)); + TRY(m_signal_handlers.try_set(sig, move(handler))); return {}; } @@ -84,7 +83,7 @@ namespace os read(m_signal_pipe[0], &sig, sizeof(int)); auto handler = m_signal_handlers.try_get(sig); - if (handler.has_value()) { handler.value()(sig); } + if (handler.has_value()) { (*handler.value_ptr())(sig); } } for (usize i = 0; i < m_fd_listeners.size(); i++) @@ -93,7 +92,7 @@ namespace os if (pfd.revents) { auto handler = m_fd_listeners.try_get(pfd.fd); - if (handler.has_value()) handler.value()(pfd.fd, pfd.revents); + if (handler.has_value()) (*handler.value_ptr())(pfd.fd, pfd.revents); } } }