io_uring: Add wait functions

This makes it possible to write trivial applications that aren't
necessarily using a full blown event loop.

Change-Id: If19cc9f0c21c029a5fda6a0d4f3eae8677841999
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/src/stdplus/io_uring.cpp b/src/stdplus/io_uring.cpp
index eaa2848..7acf2c7 100644
--- a/src/stdplus/io_uring.cpp
+++ b/src/stdplus/io_uring.cpp
@@ -12,6 +12,14 @@
 namespace stdplus
 {
 
+__kernel_timespec chronoToKTS(std::chrono::nanoseconds t) noexcept
+{
+    __kernel_timespec ts;
+    ts.tv_sec = std::chrono::floor<std::chrono::seconds>(t).count();
+    ts.tv_nsec = (t % std::chrono::seconds(1)).count();
+    return ts;
+}
+
 IoUring::IoUring(size_t queue_size)
 {
     CHECK_RET(io_uring_queue_init(queue_size, &ring, 0), "io_uring_queue_init");
@@ -109,6 +117,20 @@
     }
 }
 
+void IoUring::wait()
+{
+    io_uring_cqe* cqe;
+    CHECK_RET(io_uring_wait_cqe(&ring, &cqe), "io_uring_wait_cqe");
+}
+
+void IoUring::wait(std::chrono::nanoseconds timeout)
+{
+    io_uring_cqe* cqe;
+    auto kts = chronoToKTS(timeout);
+    CHECK_RET(io_uring_wait_cqe_timeout(&ring, &cqe, &kts),
+              "io_uring_wait_cqe_timeout");
+}
+
 stdplus::ManagedFd& IoUring::getEventFd()
 {
     if (event_fd)
diff --git a/src/stdplus/io_uring.hpp b/src/stdplus/io_uring.hpp
index f4cd269..d5f33e8 100644
--- a/src/stdplus/io_uring.hpp
+++ b/src/stdplus/io_uring.hpp
@@ -5,12 +5,16 @@
 #include <stdplus/fd/managed.hpp>
 #include <stdplus/handle/managed.hpp>
 
+#include <chrono>
 #include <optional>
 #include <vector>
 
 namespace stdplus
 {
 
+/** @brief Converts a chrono duration into a kernel duration */
+__kernel_timespec chronoToKTS(std::chrono::nanoseconds t) noexcept;
+
 class IoUring
 {
   public:
@@ -90,6 +94,10 @@
     /** @brief Non-blocking process all outstanding CQEs */
     void process() noexcept;
 
+    /** @brief Waits for new CQEs to become available */
+    void wait();
+    void wait(std::chrono::nanoseconds timeout);
+
     /** @brief Returns the EventFD associated with the ring
      *         A new descriptor is created if it does not yet exist
      *
diff --git a/test/io_uring.cpp b/test/io_uring.cpp
index 6e9981d..6b8290e 100644
--- a/test/io_uring.cpp
+++ b/test/io_uring.cpp
@@ -1,5 +1,7 @@
 #include <poll.h>
 
+#include <array>
+#include <chrono>
 #include <stdplus/io_uring.hpp>
 
 #include <gmock/gmock.h>
@@ -10,6 +12,16 @@
 
 using testing::_;
 
+TEST(Convert, ChronoToKTS)
+{
+    const auto ns = 700;
+    const auto s = 5;
+    const auto kts =
+        chronoToKTS(std::chrono::nanoseconds(ns) + std::chrono::seconds(s));
+    EXPECT_EQ(kts.tv_sec, s);
+    EXPECT_EQ(kts.tv_nsec, ns);
+}
+
 class MockHandler : public IoUring::CQEHandler
 {
   public:
@@ -123,6 +135,17 @@
     ASSERT_EQ(0, poll(&pfd, 1, 100));
 }
 
+TEST_F(IoUringTest, Wait)
+{
+    auto& sqe = ring.getSQE();
+    auto kts = chronoToKTS(std::chrono::milliseconds(1));
+    io_uring_prep_timeout(&sqe, &kts, 0, 0);
+    ring.setHandler(sqe, &h[0]);
+    ring.submit();
+    ring.wait(std::chrono::seconds(1));
+    EXPECT_CALL(h[0], handleCQE(_));
+}
+
 TEST_F(IoUringTest, HandleCalledOnDestroy)
 {
     auto& sqe = ring.getSQE();