Add a whole bunch of tests for when linked requests fail early during submission. Signed-off-by: Pavel Begunkov <asml.silence@xxxxxxxxx> --- v2: correct io_uring_submit() ret checks with !drain .gitignore | 1 + test/Makefile | 2 + test/submit-link-fail.c | 150 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 test/submit-link-fail.c diff --git a/.gitignore b/.gitignore index 3d67ef9..df0f740 100644 --- a/.gitignore +++ b/.gitignore @@ -128,6 +128,7 @@ /test/rw_merge_test /test/sqpoll-cancel-hang /test/testfile +/test/submit-link-fail /test/*.dmesg config-host.h diff --git a/test/Makefile b/test/Makefile index d392b95..775e3bb 100644 --- a/test/Makefile +++ b/test/Makefile @@ -123,6 +123,7 @@ test_targets += \ sq-space_left \ stdout \ submit-reuse \ + submit-link-fail \ symlink \ teardowns \ thread-exit \ @@ -264,6 +265,7 @@ test_srcs := \ statx.c \ stdout.c \ submit-reuse.c \ + submit-link-fail.c \ symlink.c \ teardowns.c \ thread-exit.c \ diff --git a/test/submit-link-fail.c b/test/submit-link-fail.c new file mode 100644 index 0000000..b79aa7c --- /dev/null +++ b/test/submit-link-fail.c @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Description: tests linked requests failing during submission + */ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <assert.h> + +#include "liburing.h" + +#define DRAIN_USER_DATA 42 + +static int test_underprep_fail(bool hardlink, bool drain, bool link_last, + int link_size, int fail_idx) +{ + const int invalid_fd = 42; + int link_flags = IOSQE_IO_LINK; + int total_submit = link_size; + struct io_uring ring; + struct io_uring_sqe *sqe; + struct io_uring_cqe *cqe; + char buffer[1]; + int i, ret, fds[2]; + + if (drain) + link_flags |= IOSQE_IO_DRAIN; + if (hardlink) + link_flags |= IOSQE_IO_HARDLINK; + + assert(fail_idx < link_size); + assert(link_size < 40); + + /* create a new ring as it leaves it dirty */ + ret = io_uring_queue_init(8, &ring, 0); + if (ret) { + printf("ring setup failed\n"); + return -1; + } + if (pipe(fds)) { + perror("pipe"); + return -1; + } + + if (drain) { + /* clog drain, so following reqs sent to draining */ + sqe = io_uring_get_sqe(&ring); + io_uring_prep_read(sqe, fds[0], buffer, sizeof(buffer), 0); + sqe->user_data = DRAIN_USER_DATA; + sqe->flags |= IOSQE_IO_DRAIN; + total_submit++; + } + + for (i = 0; i < link_size; i++) { + sqe = io_uring_get_sqe(&ring); + if (i == fail_idx) + io_uring_prep_read(sqe, invalid_fd, buffer, 1, 0); + else + io_uring_prep_nop(sqe); + + if (i != link_size - 1 || !link_last) + sqe->flags |= link_flags; + sqe->user_data = i; + } + + ret = io_uring_submit(&ring); + if (ret != total_submit) { + /* Old behaviour, failed early and under-submitted */ + if (ret == fail_idx + 1 + drain) + goto out; + fprintf(stderr, "submit failed: %d\n", ret); + return -1; + } + + if (drain) { + /* unclog drain */ + write(fds[1], buffer, sizeof(buffer)); + } + + for (i = 0; i < total_submit; i++) { + ret = io_uring_wait_cqe(&ring, &cqe); + if (ret) { + fprintf(stderr, "wait_cqe=%d\n", ret); + return 1; + } + + ret = cqe->res; + if (cqe->user_data == DRAIN_USER_DATA) { + if (ret != 1) { + fprintf(stderr, "drain failed %d\n", ret); + return 1; + } + } else if (cqe->user_data == fail_idx) { + if (ret == 0 || ret == -ECANCELED) { + fprintf(stderr, "half-prep req unexpected return %d\n", ret); + return 1; + } + } else { + if (ret != -ECANCELED) { + fprintf(stderr, "cancel failed %d, ud %d\n", ret, (int)cqe->user_data); + return 1; + } + } + io_uring_cqe_seen(&ring, cqe); + } +out: + close(fds[0]); + close(fds[1]); + io_uring_queue_exit(&ring); + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret, link_size, fail_idx, i; + + if (argc > 1) + return 0; + + /* + * hardlink, size=3, fail_idx=1, drain=false -- kernel fault + * link, size=3, fail_idx=0, drain=true -- kernel fault + * link, size=3, fail_idx=1, drain=true -- invalid cqe->res + */ + for (link_size = 0; link_size < 3; link_size++) { + for (fail_idx = 0; fail_idx < link_size; fail_idx++) { + for (i = 0; i < 8; i++) { + bool hardlink = (i & 1) != 0; + bool drain = (i & 2) != 0; + bool link_last = (i & 4) != 0; + + ret = test_underprep_fail(hardlink, drain, link_last, + link_size, fail_idx); + if (!ret) + continue; + + fprintf(stderr, "failed %d, hard %d, drain %d," + "link_last %d, size %d, idx %d\n", + ret, hardlink, drain, link_last, + link_size, fail_idx); + return 1; + } + } + } + + return 0; +} -- 2.33.0