io_uring: Add basic wrapper
This is a simple RAII wrapper around a ring instance that structure SQE
/ CQE callbacks to make it convenient for C++ usage.
Change-Id: I9db8b48a81bec8d8aff4a362920f4dd688c27d57
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/meson.build b/src/meson.build
index 11bebe7..2a0169a 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -68,6 +68,26 @@
error('File descriptor support required')
endif
+io_uring_dep = dependency('liburing', required: get_option('io_uring'))
+has_io_uring = false
+if not get_option('io_uring').disabled() and has_fd and io_uring_dep.found()
+ has_io_uring = true
+
+ stdplus_deps += [
+ io_uring_dep,
+ ]
+
+ stdplus_srcs += [
+ 'stdplus/io_uring.cpp',
+ ]
+
+ install_headers(
+ 'stdplus/io_uring.hpp',
+ subdir: 'stdplus')
+elif get_option('io_uring').enabled()
+ error('File descriptor support required')
+endif
+
stdplus_lib = library(
'stdplus',
stdplus_srcs,
diff --git a/src/stdplus/io_uring.cpp b/src/stdplus/io_uring.cpp
new file mode 100644
index 0000000..3ec5da1
--- /dev/null
+++ b/src/stdplus/io_uring.cpp
@@ -0,0 +1,111 @@
+#include <liburing.h>
+#include <sys/eventfd.h>
+
+#include <stdplus/fd/managed.hpp>
+#include <stdplus/fd/ops.hpp>
+#include <stdplus/io_uring.hpp>
+#include <stdplus/util/cexec.hpp>
+
+#include <algorithm>
+
+namespace stdplus
+{
+
+IoUring::IoUring(size_t queue_size)
+{
+ CHECK_RET(io_uring_queue_init(queue_size, &ring, 0), "io_uring_queue_init");
+}
+
+IoUring::~IoUring()
+{
+ io_uring_queue_exit(&ring);
+ io_uring_cqe cqe{};
+ cqe.res = -ECANCELED;
+ for (auto h : handlers)
+ {
+ h->handleCQE(cqe);
+ }
+}
+
+io_uring_sqe& IoUring::getSQE()
+{
+ return *CHECK_ERRNO(io_uring_get_sqe(&ring), "io_uring_get_sqe");
+}
+
+void IoUring::setHandler(io_uring_sqe& sqe, CQEHandler* h) noexcept
+{
+ auto oldh = reinterpret_cast<CQEHandler*>(sqe.user_data);
+ if (oldh == h)
+ {
+ return;
+ }
+ io_uring_cqe cqe{};
+ cqe.res = -ECANCELED;
+ dropHandler(oldh, cqe);
+ if (h != nullptr)
+ {
+ handlers.push_back(h);
+ }
+ io_uring_sqe_set_data(&sqe, h);
+}
+
+void IoUring::cancelHandler(CQEHandler& h)
+{
+ io_uring_prep_cancel(&getSQE(), &h, 0);
+ submit();
+}
+
+void IoUring::submit()
+{
+ CHECK_RET(io_uring_submit(&ring), "io_uring_submit");
+}
+
+void IoUring::process() noexcept
+{
+ io_uring_cqe* cqe;
+ while (io_uring_peek_cqe(&ring, &cqe) == 0)
+ {
+ auto h = reinterpret_cast<CQEHandler*>(cqe->user_data);
+ dropHandler(h, *cqe);
+ io_uring_cqe_seen(&ring, cqe);
+ }
+}
+
+stdplus::ManagedFd& IoUring::getEventFd()
+{
+ if (event_fd)
+ {
+ return *event_fd;
+ }
+ stdplus::ManagedFd efd(CHECK_RET(eventfd(0, EFD_NONBLOCK), "eventfd"));
+ CHECK_RET(io_uring_register_eventfd(&ring, efd.get()),
+ "io_uring_register_eventfd");
+ return *(event_fd = std::move(efd));
+}
+
+void IoUring::processEvents()
+{
+ auto& efd = getEventFd();
+ std::byte b[8];
+ while (!stdplus::fd::read(efd, b).empty())
+ {
+ process();
+ }
+}
+
+void IoUring::dropHandler(CQEHandler* h, io_uring_cqe& cqe) noexcept
+{
+ if (h == nullptr)
+ {
+ return;
+ }
+ auto it = std::find(handlers.begin(), handlers.end(), h);
+ if (it != handlers.end() - 1)
+ {
+ std::swap(*it, handlers.back());
+ }
+ handlers.pop_back();
+ h->handleCQE(cqe);
+}
+
+} // namespace stdplus
diff --git a/src/stdplus/io_uring.hpp b/src/stdplus/io_uring.hpp
new file mode 100644
index 0000000..d3090ed
--- /dev/null
+++ b/src/stdplus/io_uring.hpp
@@ -0,0 +1,84 @@
+#pragma once
+#include <liburing.h>
+
+#include <stdplus/fd/managed.hpp>
+
+#include <optional>
+#include <vector>
+
+namespace stdplus
+{
+
+class IoUring
+{
+ public:
+ struct CQEHandler
+ {
+ CQEHandler() = default;
+ CQEHandler(CQEHandler&&) = delete;
+ CQEHandler& operator=(CQEHandler&&) = delete;
+ CQEHandler(const CQEHandler&) = delete;
+ CQEHandler& operator=(const CQEHandler&) = delete;
+ virtual ~CQEHandler() = default;
+
+ virtual void handleCQE(io_uring_cqe&) noexcept = 0;
+ };
+
+ explicit IoUring(size_t queue_size = 10);
+ IoUring(IoUring&&) = delete;
+ IoUring& operator=(IoUring&&) = delete;
+ IoUring(const IoUring&) = delete;
+ IoUring& operator=(const IoUring&) = delete;
+ ~IoUring();
+
+ /** @brief Gets an unused SQE from the ring
+ *
+ * @throws std::system_error if the allocation fails
+ * @return An SQE on the ring
+ */
+ io_uring_sqe& getSQE();
+
+ /** @brief Associates the SQE with a user provided callback handler
+ *
+ * @param[in] sqe - The SQE that we want to register
+ * @param[in] h - The handler which will be run when the CQE comes back
+ */
+ void setHandler(io_uring_sqe& sqe, CQEHandler* h) noexcept;
+
+ /** @brief Cancels the outstanding request associated with a handler
+ *
+ * @param[in] h - The handler associated with the request
+ */
+ void cancelHandler(CQEHandler& h);
+
+ /** @brief Submits all outstanding SQEs to the kernel
+ *
+ * @throws std::system_error if the submission fails
+ */
+ void submit();
+
+ /** @brief Non-blocking process all outstanding CQEs */
+ void process() noexcept;
+
+ /** @brief Returns the EventFD associated with the ring
+ * A new descriptor is created if it does not yet exist
+ *
+ * @throws std::system_error if constructing the event fd fails
+ * @return A reference to the descriptor
+ */
+ stdplus::ManagedFd& getEventFd();
+
+ /** @brief Non-blocking process all outstanding eventFd events
+ * Should be used instead of process() to clear eventFd events.
+ */
+ void processEvents();
+
+ private:
+ io_uring ring;
+ std::optional<stdplus::ManagedFd> event_fd;
+ std::vector<CQEHandler*> handlers;
+
+ void dropHandler(CQEHandler* h, io_uring_cqe& cqe) noexcept;
+};
+
+} // namespace stdplus