blob: 477a68e4aedb45b11d5bd9271c364f3aac3cc6c7 [file] [log] [blame]
/* 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 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;
}