aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJens Axboe <axboe@kernel.dk>2024-02-13 12:00:30 -0700
committerJens Axboe <axboe@kernel.dk>2024-02-13 16:54:52 -0700
commit584ba58e65b4aa47c435cef19f1996bc059f22fb (patch)
treef6e18415dd19df2442a8e9f0690322ba6063ba29
parent012a30897f107c65d1d350f628e58a468ff13cc0 (diff)
downloadliburing-min-wait.tar.gz
test/min-timeout-wait: 2nd min time testermin-wait
This just does the wait side, and uses that helper. Mainly exists because I forgot I wrote the other test case... But more testing is always better, so let's keep it. Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r--test/Makefile1
-rw-r--r--test/min-timeout-wait.c354
2 files changed, 355 insertions, 0 deletions
diff --git a/test/Makefile b/test/Makefile
index 11d3124e..f4fc7b10 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -114,6 +114,7 @@ test_srcs := \
link-timeout.c \
madvise.c \
min-timeout.c \
+ min-timeout-wait.c \
mkdir.c \
msg-ring.c \
msg-ring-flags.c \
diff --git a/test/min-timeout-wait.c b/test/min-timeout-wait.c
new file mode 100644
index 00000000..dfb237c5
--- /dev/null
+++ b/test/min-timeout-wait.c
@@ -0,0 +1,354 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Description: run various min_timeout tests
+ *
+ */
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <pthread.h>
+
+#include "liburing.h"
+#include "helpers.h"
+
+struct data {
+ pthread_barrier_t startup;
+ unsigned long usec_sleep;
+ int out_fds[8];
+ int nr_fds;
+};
+
+static unsigned long long mtime_since(const struct timeval *s,
+ const struct timeval *e)
+{
+ long long sec, usec;
+
+ sec = e->tv_sec - s->tv_sec;
+ usec = (e->tv_usec - s->tv_usec);
+ if (sec > 0 && usec < 0) {
+ sec--;
+ usec += 1000000;
+ }
+
+ sec *= 1000;
+ usec /= 1000;
+ return sec + usec;
+}
+
+static unsigned long long mtime_since_now(struct timeval *tv)
+{
+ struct timeval end;
+
+ gettimeofday(&end, NULL);
+ return mtime_since(tv, &end);
+}
+
+static int time_pass(struct timeval *start, unsigned long min_t,
+ unsigned long max_t, const char *name)
+{
+ unsigned long elapsed;
+
+ elapsed = mtime_since_now(start);
+ if (elapsed < min_t || elapsed > max_t) {
+ fprintf(stderr, "%s fails time check\n", name);
+ fprintf(stderr, " elapsed=%lu, min=%lu, max=%lu\n", elapsed,
+ min_t, max_t);
+ return T_EXIT_FAIL;
+ }
+ return T_EXIT_PASS;
+}
+
+static void *pipe_write(void *data)
+{
+ struct data *d = data;
+ char buf[32];
+ int i;
+
+ memset(buf, 0x55, sizeof(buf));
+
+ pthread_barrier_wait(&d->startup);
+
+ if (d->usec_sleep)
+ usleep(d->usec_sleep);
+
+ for (i = 0; i < d->nr_fds; i++) {
+ int ret;
+
+ ret = write(d->out_fds[i], buf, sizeof(buf));
+ if (ret < 0) {
+ perror("write");
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static int __test_writes(struct io_uring *ring, int npipes, int usec_sleep,
+ int usec_wait, int min_t, int max_t, const char *name)
+{
+ struct __kernel_timespec ts;
+ struct io_uring_cqe *cqe;
+ struct io_uring_sqe *sqe;
+ struct timeval tv;
+ int ret, i, fds[4][2];
+ pthread_t thread;
+ struct data d;
+ char buf[32];
+ void *tret;
+
+ for (i = 0; i < npipes; i++) {
+ if (pipe(fds[i]) < 0) {
+ perror("pipe");
+ return T_EXIT_FAIL;
+ }
+ d.out_fds[i] = fds[i][1];
+ }
+ d.nr_fds = npipes;
+
+ pthread_barrier_init(&d.startup, NULL, 2);
+ d.usec_sleep = usec_sleep;
+
+ pthread_create(&thread, NULL, pipe_write, &d);
+ pthread_barrier_wait(&d.startup);
+
+ for (i = 0; i < npipes; i++) {
+ sqe = io_uring_get_sqe(ring);
+ io_uring_prep_read(sqe, fds[i][0], buf, sizeof(buf), 0);
+ }
+
+ io_uring_submit(ring);
+
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+ gettimeofday(&tv, NULL);
+ ret = io_uring_wait_cqes_min_timeout(ring, &cqe, 4, &ts, usec_wait, NULL);
+ if (ret) {
+ fprintf(stderr, "wait_cqes: %d\n", ret);
+ return T_EXIT_FAIL;
+ }
+
+ ret = time_pass(&tv, min_t, max_t, name);
+
+ io_uring_cq_advance(ring, npipes);
+
+ pthread_join(thread, &tret);
+ for (i = 0; i < npipes; i++) {
+ close(fds[i][0]);
+ close(fds[i][1]);
+ }
+ return ret;
+}
+/*
+ * Test doing min_wait for N events, where 0 events are already available
+ * on wait enter but N/2 are posted within the min_wait window. We'll expect to
+ * return when the min_wait window expires.
+ */
+static int test_some_wait(struct io_uring *ring)
+{
+ return __test_writes(ring, 2, 1000, 100000, 95, 120, __FUNCTION__);
+}
+
+/*
+ * Test doing min_wait for N events, where 0 events are already available
+ * on wait enter but N are posted within the min_wait window. We'll expect to
+ * return upon arrival of the N events, not the full min_wait window.
+ */
+static int test_post_wait(struct io_uring *ring)
+{
+ return __test_writes(ring, 4, 10000, 200000, 9, 12, __FUNCTION__);
+}
+
+/*
+ * Test doing min_wait for N events, where 0 events are already available
+ * on wait enter and one is posted after the min_wait timeout has expired.
+ * That first event should cause wait to abort, even if the task has asked
+ * for more to wait on.
+ */
+static int test_late(struct io_uring *ring)
+{
+ return __test_writes(ring, 1, 100000, 10000, 95, 120, __FUNCTION__);
+}
+
+static int __test_nop(struct io_uring *ring, int nr_nops, int min_t, int max_t,
+ unsigned long long_wait, const char *name)
+{
+ struct __kernel_timespec ts;
+ struct io_uring_cqe *cqe;
+ struct timeval tv;
+ int i, ret;
+
+ for (i = 0; i < nr_nops; i++) {
+ struct io_uring_sqe *sqe;
+
+ sqe = io_uring_get_sqe(ring);
+ io_uring_prep_nop(sqe);
+ }
+
+ if (nr_nops)
+ io_uring_submit(ring);
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = long_wait * 1000;
+ gettimeofday(&tv, NULL);
+ ret = io_uring_wait_cqes_min_timeout(ring, &cqe, 4, &ts, 50000, NULL);
+ io_uring_cq_advance(ring, nr_nops);
+ if (nr_nops) {
+ if (ret) {
+ fprintf(stderr, "wait_cqes: %d\n", ret);
+ return T_EXIT_FAIL;
+ }
+ } else {
+ if (ret != -ETIME) {
+ fprintf(stderr, "wait_cqes: %d\n", ret);
+ return T_EXIT_FAIL;
+ }
+ }
+
+ return time_pass(&tv, min_t, max_t, name);
+}
+
+/*
+ * Test doing min_wait for N events, where N/2 events are already available
+ * on wait enter. This should abort waiting after min_wait, not do the full
+ * wait.
+ */
+static int test_some(struct io_uring *ring)
+{
+ return __test_nop(ring, 2, 45, 55, 100000, __FUNCTION__);
+}
+
+/*
+ * Test doing min_wait for N events, where N events are already available
+ * on wait enter.
+ */
+static int test_already(struct io_uring *ring)
+{
+ return __test_nop(ring, 4, 0, 1, 100000, __FUNCTION__);
+}
+
+/*
+ * Test doing min_wait for N events, and nothing ever gets posted. We'd
+ * expect the time to be the normal wait time, not the min_wait time.
+ */
+static int test_nothing(struct io_uring *ring)
+{
+ return __test_nop(ring, 0, 95, 110, 100000, __FUNCTION__);
+}
+
+/*
+ * Test doing min_wait for N events, and nothing ever gets posted, and use
+ * a min_wait time that's bigger than the total wait. We only expect the
+ * min_wait to elapse.
+ */
+static int test_min_wait_biggest(struct io_uring *ring)
+{
+ return __test_nop(ring, 0, 45, 55, 20000, __FUNCTION__);
+}
+
+/*
+ * Test doing min_wait for N events, and nothing ever gets posted, and use
+ * a min_wait time that's roughly equal to the total wait. We only expect the
+ * min_wait to elapse.
+ */
+static int test_min_wait_equal(struct io_uring *ring)
+{
+ return __test_nop(ring, 0, 45, 55, 50001, __FUNCTION__);
+}
+
+int main(int argc, char *argv[])
+{
+ struct io_uring ring1, ring2;
+ struct io_uring_params p = { };
+ int ret;
+
+ if (argc > 1)
+ return 0;
+
+ ret = t_create_ring_params(8, &ring1, &p);
+ if (ret == T_SETUP_SKIP)
+ return T_EXIT_SKIP;
+ else if (ret != T_SETUP_OK)
+ return ret;
+ if (!(p.features & IORING_FEAT_MIN_TIMEOUT))
+ return T_EXIT_SKIP;
+
+ p.flags = IORING_SETUP_SINGLE_ISSUER|IORING_SETUP_DEFER_TASKRUN;
+ ret = t_create_ring_params(8, &ring2, &p);
+ if (ret == T_SETUP_SKIP)
+ return T_EXIT_SKIP;
+ else if (ret != T_SETUP_OK)
+ return ret;
+
+ ret = test_already(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_already(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ ret = test_some(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_some(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ ret = test_late(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_late(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ ret = test_post_wait(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_post_wait(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ ret = test_some_wait(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_some_wait(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ ret = test_nothing(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_nothing(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ ret = test_min_wait_biggest(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_min_wait_biggest(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ ret = test_min_wait_equal(&ring1);
+ if (ret == T_EXIT_FAIL || ret == T_EXIT_SKIP)
+ return ret;
+
+ ret = test_min_wait_equal(&ring2);
+ if (ret == T_EXIT_FAIL)
+ return T_EXIT_FAIL;
+
+ io_uring_queue_exit(&ring1);
+ io_uring_queue_exit(&ring2);
+ return T_EXIT_PASS;
+}