/* SPDX-License-Identifier: MIT */ /* * Description: run various min_timeout tests * */ #include #include #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" struct data { pthread_barrier_t startup; unsigned long usec_sleep; int out_fds[8]; int nr_fds; }; 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; }