/* SPDX-License-Identifier: MIT */ /* * Description: test min timeout handling * */ #include #include #include #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" #define NPIPES 8 #define NWRITES 6 #define WAIT_USEC (250000) static int no_min_timeout; struct d { int fd[NPIPES]; long delay; }; static void *thread_fn(void *data) { struct d *d = data; char buf[32]; int i; memset(buf, 0x55, sizeof(buf)); for (i = 0; i < NWRITES; i++) { int ret; usleep(d->delay); ret = write(d->fd[i], buf, sizeof(buf)); if (ret != sizeof(buf)) { fprintf(stderr, "bad write %d\n", ret); break; } } return NULL; } /* * Allow 25% tolerance */ static int within_range(unsigned int target, unsigned int msec) { unsigned int high, low; low = (target * 3) / 4; high = (target * 5) / 4; return (msec >= low && msec <= high); } static int test(int flags, int expected_ctx, int min_wait, int write_delay, int nr_cqes, int msec_target) { struct io_uring_cqe *cqe; struct io_uring_sqe *sqe; struct io_uring ring; struct __kernel_timespec ts; struct rusage s, e; pthread_t thread; struct d d; struct io_uring_params p = { .flags = flags, }; int ret, fds[NPIPES][2], i; struct timeval start_time; char buf[32]; void *tret; long ttime; ret = io_uring_queue_init_params(NPIPES, &ring, &p); if (ret == -EINVAL) return T_EXIT_SKIP; if (!(p.features & IORING_FEAT_MIN_TIMEOUT)) { no_min_timeout = 1; return T_EXIT_SKIP; } for (i = 0; i < NPIPES; i++) { if (pipe(fds[i]) < 0) { perror("pipe"); return 1; } d.fd[i] = fds[i][1]; } d.delay = write_delay; pthread_create(&thread, NULL, thread_fn, &d); for (i = 0; i < NPIPES; i++) { sqe = io_uring_get_sqe(&ring); io_uring_prep_read(sqe, fds[i][0], buf, sizeof(buf), 0); } ts.tv_sec = 0; ts.tv_nsec = WAIT_USEC * 1000LL; gettimeofday(&start_time, NULL); getrusage(RUSAGE_THREAD, &s); ret = io_uring_submit_and_wait_min_timeout(&ring, &cqe, 8, &ts, min_wait, NULL); if (ret != NPIPES) fprintf(stderr, "submit_and_wait=%d\n", ret); getrusage(RUSAGE_THREAD, &e); e.ru_nvcsw -= s.ru_nvcsw; ttime = mtime_since_now(&start_time); if (!within_range(msec_target, ttime)) { fprintf(stderr, "Expected %d msec, got %ld msec\n", msec_target, ttime); fprintf(stderr, "flags=%x, min_wait=%d, write_delay=%d\n", flags, min_wait, write_delay); } /* will usually be accurate, but allow for offset of 1 */ if (e.ru_nvcsw != expected_ctx && (e.ru_nvcsw - expected_ctx > 1)) fprintf(stderr, "%ld ctx switches, expected %d\n", e.ru_nvcsw, expected_ctx); for (i = 0; i < NPIPES; i++) { ret = io_uring_peek_cqe(&ring, &cqe); if (ret) break; io_uring_cqe_seen(&ring, cqe); } if (i != nr_cqes) fprintf(stderr, "Got %d CQEs, expected %d\n", i, nr_cqes); pthread_join(thread, &tret); for (i = 0; i < NPIPES; i++) { close(fds[i][0]); close(fds[i][1]); } return T_EXIT_PASS; } int main(int argc, char *argv[]) { int ret; if (argc > 1) return T_EXIT_SKIP; ret = test(0, NWRITES + 1, 0, 2000, NWRITES, WAIT_USEC / 1000); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; if (no_min_timeout) return T_EXIT_SKIP; ret = test(0, NWRITES + 1, 50000, 2000, NWRITES, 50); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; ret = test(0, NWRITES + 1, 500000, 2000, NWRITES, 500); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; /* no writes within min timeout, but it's given. expect 1 cqe */ ret = test(0, 1, 10000, 20000, 1, 20); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; /* same as above, but no min timeout. should time out and we get 6 */ ret = test(0, NWRITES + 1, 0, 20000, NWRITES, WAIT_USEC / 1000); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; ret = test(IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN, 1, 0, 2000, NWRITES, WAIT_USEC / 1000); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; ret = test(IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN, 1, 50000, 2000, NWRITES, 50); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; ret = test(IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN, 1, 500000, 2000, NWRITES, 500); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; /* no writes within min timeout, but it's given. expect 1 cqe */ ret = test(IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN, 1, 10000, 20000, 1, 20); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; /* same as above, but no min timeout. should time out and we get 6 */ ret = test(IORING_SETUP_SINGLE_ISSUER | IORING_SETUP_DEFER_TASKRUN, 1, 0, 20000, NWRITES, WAIT_USEC / 1000); if (ret == T_EXIT_FAIL) return T_EXIT_FAIL; return ret; }