diff options
author | Jens Axboe <axboe@kernel.dk> | 2024-02-13 12:00:30 -0700 |
---|---|---|
committer | Jens Axboe <axboe@kernel.dk> | 2024-02-13 16:54:52 -0700 |
commit | 584ba58e65b4aa47c435cef19f1996bc059f22fb (patch) | |
tree | f6e18415dd19df2442a8e9f0690322ba6063ba29 | |
parent | 012a30897f107c65d1d350f628e58a468ff13cc0 (diff) | |
download | liburing-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/Makefile | 1 | ||||
-rw-r--r-- | test/min-timeout-wait.c | 354 |
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; +} |