diff --git a/emper/io/Future.cpp b/emper/io/Future.cpp index 13e3be6179288c3ca174e67125e3f435d83d344d..4e915bb041a77b0d4c507ca20c39cdc6cbd5180d 100644 --- a/emper/io/Future.cpp +++ b/emper/io/Future.cpp @@ -17,6 +17,8 @@ #include "io/Operation.hpp" // for emper::io::Operation #include "io/Stats.hpp" // for emper::io::Stats +struct __kernel_timespec; + namespace emper::io { template <CallerEnvironment callerEnvironment> void Future::tryComplete(int32_t res, bool syscall) { @@ -132,6 +134,12 @@ void Future::prepareSqe(struct io_uring_sqe *sqe) { case Operation::CLOSE: io_uring_prep_close(sqe, fd); break; + case Operation::LINK_TIMEOUT: + io_uring_prep_link_timeout(sqe, (struct ::__kernel_timespec *)buf, 0); + break; + case Operation::TIMEOUT: + io_uring_prep_timeout(sqe, (struct ::__kernel_timespec *)buf, 0, 0); + break; default: abort(); } diff --git a/emper/io/Future.hpp b/emper/io/Future.hpp index 10b58570c201d0bc0dc681181e7ce67d69a521ef..419981e7b8a6b425aad52d86f3589e00bf3afc60 100644 --- a/emper/io/Future.hpp +++ b/emper/io/Future.hpp @@ -111,7 +111,9 @@ class Future : public Logger<LogSubsystem::IO> { * set errno to ECANCELED * * See: <a href="https://unixism.net/loti/tutorial/link_liburing.html">Liburing linking - * requests</a> The emper equivalent to the example from the liburing documentation. + * requests</a> + * The emper equivalent to the example from the liburing documentation. + * * @code * int fd = open(FILE_NAME, O_RDWR|O_TRUNC|O_CREAT, 0644); * if (fd < 0 ) { @@ -221,4 +223,39 @@ class CloseFuture : public Future { public: CloseFuture(int fildes) : Future(Operation::CLOSE, fildes, nullptr, 0, 0){}; }; + +/* + * @brief Add a timeout to any future. + * + * A Timeout is added to a Future by inserting an dependent IOURING_OP_LINK_TIMEOUT + * sqe after the actual IO operation. + * The dependent timeout will not be started like usual dependent requests after + * the previous request is completed, it is armed instead when the actual request is started. + * See: https://lwn.net/Articles/803932/ + * + * @code + * ReadFuture rf(fd, buf, buf_len, 0); + * struct __kernel_timespec ts = {.tv_sec = 0, .tv_nsec = 0); + * TimeoutWrapper t(rf, ts); + * // Both futures should be awaited by calling its wait method to + * // prevent use after free problems. + * t.submitAndWait() + * rf.Wait() + * @endcode + */ +class TimeoutWrapper : public Future { + public: + TimeoutWrapper(Future& future, struct __kernel_timespec& ts) + : Future(Operation::LINK_TIMEOUT, 0, (void*)&ts, 0, 0) { + addDependency(future); + }; +}; + +/* + * @brief Arm a timeout which will signal the future when it is reached + */ +class AlarmFuture : public Future { + public: + AlarmFuture(struct __kernel_timespec& ts) : Future(Operation::TIMEOUT, 0, (void*)&ts, 0, 0){}; +}; } // namespace emper::io diff --git a/emper/io/Operation.cpp b/emper/io/Operation.cpp index 3b4c46a0864a1194a07571d4aa3b28785d3d2e3e..66daee9c6a9a6fb973ac7933f5a9ad9f758a9082 100644 --- a/emper/io/Operation.cpp +++ b/emper/io/Operation.cpp @@ -29,6 +29,12 @@ auto operator<<(std::ostream& os, const Operation& op) -> std::ostream& { case Operation::CLOSE: os << "close"; break; + case Operation::LINK_TIMEOUT: + os << "linked timeout"; + break; + case Operation::TIMEOUT: + os << "timeout"; + break; default: abort(); } diff --git a/emper/io/Operation.hpp b/emper/io/Operation.hpp index 64d08b54f6342440b21727db22d148a0fb4c2ded..b9026b8dbac4dfbba9aebf2eb22b3b7e67210440 100644 --- a/emper/io/Operation.hpp +++ b/emper/io/Operation.hpp @@ -6,7 +6,18 @@ namespace emper::io { -enum class Operation { SEND = 0, RECV, CONNECT, ACCEPT, READ, WRITE, CLOSE, NUMBER_OF_OPERATIONS }; +enum class Operation { + SEND = 0, + RECV, + CONNECT, + ACCEPT, + READ, + WRITE, + CLOSE, + LINK_TIMEOUT, + TIMEOUT, + NUMBER_OF_OPERATIONS +}; auto operator<<(std::ostream& os, const Operation& o) -> std::ostream&; diff --git a/tests/AlarmFutureTest.cpp b/tests/AlarmFutureTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..537c5a7939e8848d3e95d16ef5d70df689ec42d0 --- /dev/null +++ b/tests/AlarmFutureTest.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// Copyright © 2020 Florian Schmaus +#include <linux/time_types.h> // for struct __kernel_timespec +#include <sys/types.h> // ssize_t + +#include <cassert> // for assert +#include <cerrno> // for errno +#include <chrono> // for high_resolution_clock +#include <compare> // for operator>=, strong_ordering +#include <cstdlib> // for exit, EXIT_FAILURE, EXIT_SUC... + +#include "Fiber.hpp" // for Fiber +#include "Runtime.hpp" // for Runtime +#include "emper-common.h" // for UNUSED_ARG +#include "io/Future.hpp" // for Future + +using emper::io::AlarmFuture; + +void emperTest() { + struct __kernel_timespec ts = {.tv_sec = 1, .tv_nsec = 0}; + AlarmFuture alarm(ts); + + auto start = std::chrono::high_resolution_clock::now(); + ssize_t res = alarm.submitAndWait(); + auto end = std::chrono::high_resolution_clock::now(); + + // timeouts return -1 if they trigger + assert(res == -1); + // the timeout was triggered + assert(errno == ETIME); + + assert(std::chrono::duration_cast<std::chrono::microseconds>(end - start) >= + std::chrono::seconds(1)); + + exit(EXIT_SUCCESS); +} + +auto main(UNUSED_ARG int arg, UNUSED_ARG char* argv[]) -> int { + Runtime runtime; + + Fiber* fiber = Fiber::from(emperTest); + runtime.scheduleFromAnywhere(*fiber); + + runtime.waitUntilFinished(); + + return EXIT_FAILURE; +} diff --git a/tests/meson.build b/tests/meson.build index 057cfeb25be0ed3950aa528057e9e05192226da0..4e72e982d0f837dd0170e27baf79af5bd7481b3b 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -86,6 +86,12 @@ tests = { 'description': 'Test linking Future objects', 'test_suite': 'io', }, + 'AlarmFutureTest.cpp': + { + 'feature_flags': ['io'], + 'description': 'Test AlarmFuture object based timeouts', + 'test_suite': 'io', + }, 'SimpleDiskAndNetworkTest.cpp': { 'feature_flags': ['io'],