Add a test specifically for IORING_SETUP_DEFER_TASKRUN Signed-off-by: Dylan Yudaken <dylany@xxxxxx> --- test/Makefile | 1 + test/defer-taskrun.c | 333 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 test/defer-taskrun.c diff --git a/test/Makefile b/test/Makefile index 418c11c95875..78a499a357d7 100644 --- a/test/Makefile +++ b/test/Makefile @@ -62,6 +62,7 @@ test_srcs := \ d4ae271dfaae.c \ d77a67ed5f27.c \ defer.c \ + defer-taskrun.c \ double-poll-crash.c \ drop-submit.c \ eeed8b54e0df.c \ diff --git a/test/defer-taskrun.c b/test/defer-taskrun.c new file mode 100644 index 000000000000..aec8c5d3f223 --- /dev/null +++ b/test/defer-taskrun.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: MIT +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <error.h> +#include <sys/eventfd.h> +#include <signal.h> +#include <poll.h> +#include <assert.h> +#include <pthread.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include "liburing.h" +#include "test.h" +#include "helpers.h" + +#define EXEC_FILENAME ".defer-taskrun" +#define EXEC_FILESIZE (1U<<20) + +static bool can_read_t(int fd, int time) +{ + int ret; + struct pollfd p = { + .fd = fd, + .events = POLLIN, + }; + + ret = poll(&p, 1, time); + + return ret == 1; +} + +static bool can_read(int fd) +{ + return can_read_t(fd, 0); +} + +static void eventfd_clear(int fd) +{ + uint64_t val; + int ret; + + assert(can_read(fd)); + ret = read(fd, &val, 8); + assert(ret == 8); +} + +static void eventfd_trigger(int fd) +{ + uint64_t val = 1; + int ret; + + ret = write(fd, &val, sizeof(val)); + assert(ret == sizeof(val)); +} + +#define CHECK(x) if (!(x)) { \ + fprintf(stderr, "%s:%d %s failed\n", __FILE__, __LINE__, #x); \ + return -1; } + +static int test_eventfd(void) +{ + struct io_uring ring; + int ret; + int fda, fdb; + struct io_uring_cqe *cqe; + + ret = io_uring_queue_init(8, &ring, IORING_SETUP_SINGLE_ISSUER | + IORING_SETUP_DEFER_TASKRUN); + if (ret) + return ret; + + fda = eventfd(0, EFD_NONBLOCK); + fdb = eventfd(0, EFD_NONBLOCK); + + CHECK(fda >= 0 && fdb >= 0); + + ret = io_uring_register_eventfd(&ring, fda); + if (ret) + return ret; + + CHECK(!can_read(fda)); + CHECK(!can_read(fdb)); + + io_uring_prep_poll_add(io_uring_get_sqe(&ring), fdb, POLLIN); + io_uring_submit(&ring); + CHECK(!can_read(fda)); /* poll should not have completed */ + + io_uring_prep_nop(io_uring_get_sqe(&ring)); + io_uring_submit(&ring); + CHECK(can_read(fda)); /* nop should have */ + + CHECK(io_uring_peek_cqe(&ring, &cqe) == 0); + CHECK(cqe->res == 0); + io_uring_cqe_seen(&ring, cqe); + eventfd_clear(fda); + + eventfd_trigger(fdb); + /* can take time due to rcu_call */ + CHECK(can_read_t(fda, 1000)); + + /* should not have processed the cqe yet */ + CHECK(io_uring_cq_ready(&ring) == 0); + + io_uring_get_events(&ring); + CHECK(io_uring_cq_ready(&ring) == 1); + + + io_uring_queue_exit(&ring); + return 0; +} + +struct thread_data { + struct io_uring ring; + int efd; + char buff[8]; +}; + +void *thread(void *t) +{ + struct thread_data *td = t; + + io_uring_prep_read(io_uring_get_sqe(&td->ring), td->efd, td->buff, sizeof(td->buff), 0); + io_uring_submit(&td->ring); + + return NULL; +} + +static int test_thread_shutdown(void) +{ + pthread_t t1; + int ret; + struct thread_data td; + struct io_uring_cqe *cqe; + uint64_t val = 1; + + ret = io_uring_queue_init(8, &td.ring, IORING_SETUP_SINGLE_ISSUER | + IORING_SETUP_DEFER_TASKRUN); + if (ret) + return ret; + + /* check that even before submitting we don't get errors */ + CHECK(io_uring_get_events(&td.ring) == 0); + + td.efd = eventfd(0, 0); + CHECK(td.efd >= 0); + + CHECK(pthread_create(&t1, NULL, thread, &td) == 0); + CHECK(pthread_join(t1, NULL) == 0); + + CHECK(write(td.efd, &val, sizeof(val)) == sizeof(val)); + CHECK(io_uring_wait_cqe(&td.ring, &cqe) == -EEXIST); + + close(td.efd); + io_uring_queue_exit(&td.ring); + return 0; +} + +static int test_exec(const char *filename) +{ + int ret; + int fd; + struct io_uring ring; + pid_t fork_pid; + static char * const new_argv[] = {"1", "2", "3", NULL}; + static char * const new_env[] = {NULL}; + char *buff; + + fork_pid = fork(); + CHECK(fork_pid >= 0); + if (fork_pid > 0) { + int wstatus; + + CHECK(waitpid(fork_pid, &wstatus, 0) != (pid_t)-1); + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != T_EXIT_SKIP) { + fprintf(stderr, "child failed %i\n", WEXITSTATUS(wstatus)); + return -1; + } + return 0; + } + + ret = io_uring_queue_init(8, &ring, IORING_SETUP_SINGLE_ISSUER | + IORING_SETUP_DEFER_TASKRUN); + if (ret) + return ret; + + if (filename) { + fd = open(filename, O_RDONLY | O_DIRECT); + } else { + t_create_file(EXEC_FILENAME, EXEC_FILESIZE); + fd = open(EXEC_FILENAME, O_RDONLY | O_DIRECT); + unlink(EXEC_FILENAME); + } + buff = (char*)malloc(EXEC_FILESIZE); + CHECK(posix_memalign((void **)&buff, 4096, EXEC_FILESIZE) == 0); + CHECK(buff); + + CHECK(fd >= 0); + io_uring_prep_read(io_uring_get_sqe(&ring), fd, buff, EXEC_FILESIZE, 0); + io_uring_submit(&ring); + ret = execve("/proc/self/exe", new_argv, new_env); + /* if we get here it failed anyway */ + fprintf(stderr, "execve failed %d\n", ret); + return -1; +} + +static int test_flag(void) +{ + struct io_uring ring; + int ret; + int fd; + struct io_uring_cqe *cqe; + + ret = io_uring_queue_init(8, &ring, IORING_SETUP_SINGLE_ISSUER | + IORING_SETUP_DEFER_TASKRUN | + IORING_SETUP_TASKRUN_FLAG); + CHECK(!ret); + + fd = eventfd(0, EFD_NONBLOCK); + CHECK(fd >= 0); + + io_uring_prep_poll_add(io_uring_get_sqe(&ring), fd, POLLIN); + io_uring_submit(&ring); + CHECK(!can_read(fd)); /* poll should not have completed */ + + eventfd_trigger(fd); + CHECK(can_read(fd)); + + /* should not have processed the poll cqe yet */ + CHECK(io_uring_cq_ready(&ring) == 0); + + /* flag should be set */ + CHECK(IO_URING_READ_ONCE(*ring.sq.kflags) & IORING_SQ_TASKRUN); + + /* Specifically peek, knowing we have only no cqe + * but because the flag is set, liburing should try and get more + */ + ret = io_uring_peek_cqe(&ring, &cqe); + + CHECK(ret == 0 && cqe); + CHECK(!(IO_URING_READ_ONCE(*ring.sq.kflags) & IORING_SQ_TASKRUN)); + + close(fd); + io_uring_queue_exit(&ring); + return 0; +} + +static int test_ring_shutdown(void) +{ + struct io_uring ring; + int ret; + int fd[2]; + char buff = '\0'; + char send = 'X'; + + ret = io_uring_queue_init(8, &ring, IORING_SETUP_SINGLE_ISSUER | + IORING_SETUP_DEFER_TASKRUN | + IORING_SETUP_TASKRUN_FLAG); + CHECK(!ret); + + ret = t_create_socket_pair(fd, true); + CHECK(!ret); + + io_uring_prep_recv(io_uring_get_sqe(&ring), fd[0], &buff, 1, 0); + io_uring_submit(&ring); + + ret = write(fd[1], &send, 1); + CHECK(ret == 1); + + /* should not have processed the poll cqe yet */ + CHECK(io_uring_cq_ready(&ring) == 0); + io_uring_queue_exit(&ring); + + /* task work should have been processed by now */ + CHECK(buff = 'X'); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret; + const char *filename = NULL; + + if (argc > 2) + return T_EXIT_SKIP; + if (argc == 2) { + /* This test exposes interesting behaviour with a null-blk + * device configured like: + * $ modprobe null-blk completion_nsec=100000000 irqmode=2 + * and then run with $ defer-taskrun.t /dev/nullb0 + */ + filename = argv[1]; + } + + if (!t_probe_defer_taskrun()) + return T_EXIT_SKIP; + + ret = test_thread_shutdown(); + if (ret) { + fprintf(stderr, "test_thread_shutdown failed\n"); + return T_EXIT_FAIL; + } + + ret = test_exec(filename); + if (ret) { + fprintf(stderr, "test_exec failed\n"); + return T_EXIT_FAIL; + } + + ret = test_eventfd(); + if (ret) { + fprintf(stderr, "eventfd failed\n"); + return T_EXIT_FAIL; + } + + ret = test_flag(); + if (ret) { + fprintf(stderr, "flag failed\n"); + return T_EXIT_FAIL; + } + + ret = test_ring_shutdown(); + if (ret) { + fprintf(stderr, "test_ring_shutdown failed\n"); + return T_EXIT_FAIL; + } + + return T_EXIT_PASS; +} -- 2.30.2