diff options
| author | ilnaz <[email protected]> | 2022-12-13 16:01:38 +0300 |
|---|---|---|
| committer | ilnaz <[email protected]> | 2022-12-13 16:01:38 +0300 |
| commit | f2bea70bea01921ec43846224d100f2c70dd5719 (patch) | |
| tree | eead917572063b63adc1c9a76284c8fbd10f25a3 /contrib/libs/liburing/test/cq-overflow.c | |
| parent | 1ab9ee3dfe0ab4023a3a57bf55de31dff3eac908 (diff) | |
Add cross-link
Diffstat (limited to 'contrib/libs/liburing/test/cq-overflow.c')
| -rw-r--r-- | contrib/libs/liburing/test/cq-overflow.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/contrib/libs/liburing/test/cq-overflow.c b/contrib/libs/liburing/test/cq-overflow.c new file mode 100644 index 00000000000..f43e19c013f --- /dev/null +++ b/contrib/libs/liburing/test/cq-overflow.c @@ -0,0 +1,525 @@ +#include "../config-host.h" +/* SPDX-License-Identifier: MIT */ +/* + * Description: run various CQ ring overflow tests + * + */ +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <assert.h> + +#include "helpers.h" +#include "liburing.h" + +#define FILE_SIZE (256 * 1024) +#define BS 4096 +#define BUFFERS (FILE_SIZE / BS) + +static struct iovec *vecs; + +#define ENTRIES 8 + +/* + * io_uring has rare cases where CQEs are lost. + * This happens when there is no space in the CQ ring, and also there is no + * GFP_ATOMIC memory available. In reality this probably means that the process + * is about to be killed as many other things might start failing, but we still + * want to test that liburing and the kernel deal with this properly. The fault + * injection framework allows us to test this scenario. Unfortunately this + * requires some system wide changes and so we do not enable this by default. + * The tests in this file should work in both cases (where overflows are queued + * and where they are dropped) on recent kernels. + * + * In order to test dropped CQEs you should enable fault injection in the kernel + * config: + * + * CONFIG_FAULT_INJECTION=y + * CONFIG_FAILSLAB=y + * CONFIG_FAULT_INJECTION_DEBUG_FS=y + * + * and then run the test as follows: + * echo Y > /sys/kernel/debug/failslab/task-filter + * echo 100 > /sys/kernel/debug/failslab/probability + * echo 0 > /sys/kernel/debug/failslab/verbose + * echo 100000 > /sys/kernel/debug/failslab/times + * bash -c "echo 1 > /proc/self/make-it-fail && exec ./cq-overflow.t" + */ + +static int test_io(const char *file, unsigned long usecs, unsigned *drops, int fault) +{ + struct io_uring_sqe *sqe; + struct io_uring_cqe *cqe; + struct io_uring_params p; + unsigned reaped, total; + struct io_uring ring; + int nodrop, i, fd, ret; + bool cqe_dropped = false; + + fd = open(file, O_RDONLY | O_DIRECT); + if (fd < 0) { + perror("file open"); + return 1; + } + + memset(&p, 0, sizeof(p)); + ret = io_uring_queue_init_params(ENTRIES, &ring, &p); + if (ret) { + close(fd); + fprintf(stderr, "ring create failed: %d\n", ret); + return 1; + } + nodrop = 0; + if (p.features & IORING_FEAT_NODROP) + nodrop = 1; + + total = 0; + for (i = 0; i < BUFFERS / 2; i++) { + off_t offset; + + sqe = io_uring_get_sqe(&ring); + if (!sqe) { + fprintf(stderr, "sqe get failed\n"); + goto err; + } + offset = BS * (rand() % BUFFERS); + if (fault && i == ENTRIES + 4) + vecs[i].iov_base = NULL; + io_uring_prep_readv(sqe, fd, &vecs[i], 1, offset); + + ret = io_uring_submit(&ring); + if (nodrop && ret == -EBUSY) { + *drops = 1; + total = i; + break; + } else if (ret != 1) { + fprintf(stderr, "submit got %d, wanted %d\n", ret, 1); + total = i; + break; + } + total++; + } + + if (*drops) + goto reap_it; + + usleep(usecs); + + for (i = total; i < BUFFERS; i++) { + off_t offset; + + sqe = io_uring_get_sqe(&ring); + if (!sqe) { + fprintf(stderr, "sqe get failed\n"); + goto err; + } + offset = BS * (rand() % BUFFERS); + io_uring_prep_readv(sqe, fd, &vecs[i], 1, offset); + + ret = io_uring_submit(&ring); + if (nodrop && ret == -EBUSY) { + *drops = 1; + break; + } else if (ret != 1) { + fprintf(stderr, "submit got %d, wanted %d\n", ret, 1); + break; + } + total++; + } + +reap_it: + reaped = 0; + do { + if (nodrop && !cqe_dropped) { + /* nodrop should never lose events unless cqe_dropped */ + if (reaped == total) + break; + } else { + if (reaped + *ring.cq.koverflow == total) + break; + } + ret = io_uring_wait_cqe(&ring, &cqe); + if (nodrop && ret == -EBADR) { + cqe_dropped = true; + continue; + } else if (ret) { + fprintf(stderr, "wait_cqe=%d\n", ret); + goto err; + } + if (cqe->res != BS) { + if (!(fault && cqe->res == -EFAULT)) { + fprintf(stderr, "cqe res %d, wanted %d\n", + cqe->res, BS); + goto err; + } + } + io_uring_cqe_seen(&ring, cqe); + reaped++; + } while (1); + + if (!io_uring_peek_cqe(&ring, &cqe)) { + fprintf(stderr, "found unexpected completion\n"); + goto err; + } + + if (!nodrop || cqe_dropped) { + *drops = *ring.cq.koverflow; + } else if (*ring.cq.koverflow) { + fprintf(stderr, "Found %u overflows\n", *ring.cq.koverflow); + goto err; + } + + io_uring_queue_exit(&ring); + close(fd); + return 0; +err: + if (fd != -1) + close(fd); + io_uring_queue_exit(&ring); + return 1; +} + +static int reap_events(struct io_uring *ring, unsigned nr_events, int do_wait) +{ + struct io_uring_cqe *cqe; + int i, ret = 0, seq = 0; + unsigned int start_overflow = *ring->cq.koverflow; + bool dropped = false; + + for (i = 0; i < nr_events; i++) { + if (do_wait) + ret = io_uring_wait_cqe(ring, &cqe); + else + ret = io_uring_peek_cqe(ring, &cqe); + if (do_wait && ret == -EBADR) { + unsigned int this_drop = *ring->cq.koverflow - + start_overflow; + + dropped = true; + start_overflow = *ring->cq.koverflow; + assert(this_drop > 0); + i += (this_drop - 1); + continue; + } else if (ret) { + if (ret != -EAGAIN) + fprintf(stderr, "cqe peek failed: %d\n", ret); + break; + } + if (!dropped && cqe->user_data != seq) { + fprintf(stderr, "cqe sequence out-of-order\n"); + fprintf(stderr, "got %d, wanted %d\n", (int) cqe->user_data, + seq); + return -EINVAL; + } + seq++; + io_uring_cqe_seen(ring, cqe); + } + + return i ? i : ret; +} + +/* + * Submit some NOPs and watch if the overflow is correct + */ +static int test_overflow(void) +{ + struct io_uring ring; + struct io_uring_params p; + struct io_uring_sqe *sqe; + unsigned pending; + int ret, i, j; + + memset(&p, 0, sizeof(p)); + ret = io_uring_queue_init_params(4, &ring, &p); + if (ret) { + fprintf(stderr, "io_uring_queue_init failed %d\n", ret); + return 1; + } + + /* submit 4x4 SQEs, should overflow the ring by 8 */ + pending = 0; + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + sqe = io_uring_get_sqe(&ring); + if (!sqe) { + fprintf(stderr, "get sqe failed\n"); + goto err; + } + + io_uring_prep_nop(sqe); + sqe->user_data = (i * 4) + j; + } + + ret = io_uring_submit(&ring); + if (ret == 4) { + pending += 4; + continue; + } + if (p.features & IORING_FEAT_NODROP) { + if (ret == -EBUSY) + break; + } + fprintf(stderr, "sqe submit failed: %d\n", ret); + goto err; + } + + /* we should now have 8 completions ready */ + ret = reap_events(&ring, pending, 0); + if (ret < 0) + goto err; + + if (!(p.features & IORING_FEAT_NODROP)) { + if (*ring.cq.koverflow != 8) { + fprintf(stderr, "cq ring overflow %d, expected 8\n", + *ring.cq.koverflow); + goto err; + } + } + io_uring_queue_exit(&ring); + return 0; +err: + io_uring_queue_exit(&ring); + return 1; +} + + +static void submit_one_nop(struct io_uring *ring, int ud) +{ + struct io_uring_sqe *sqe; + int ret; + + sqe = io_uring_get_sqe(ring); + assert(sqe); + io_uring_prep_nop(sqe); + sqe->user_data = ud; + ret = io_uring_submit(ring); + assert(ret == 1); +} + +/* + * Create an overflow condition and ensure that SQEs are still processed + */ +static int test_overflow_handling(bool batch, int cqe_multiple, bool poll, + bool defer) +{ + struct io_uring ring; + struct io_uring_params p; + int ret, i, j, ud, cqe_count; + unsigned int count; + int const N = 8; + int const LOOPS = 128; + int const QUEUE_LENGTH = 1024; + int completions[N]; + int queue[QUEUE_LENGTH]; + int queued = 0; + int outstanding = 0; + bool cqe_dropped = false; + + memset(&completions, 0, sizeof(int) * N); + memset(&p, 0, sizeof(p)); + p.cq_entries = 2 * cqe_multiple; + p.flags |= IORING_SETUP_CQSIZE; + + if (poll) + p.flags |= IORING_SETUP_IOPOLL; + + if (defer) + p.flags |= IORING_SETUP_SINGLE_ISSUER | + IORING_SETUP_DEFER_TASKRUN; + + ret = io_uring_queue_init_params(2, &ring, &p); + if (ret) { + fprintf(stderr, "io_uring_queue_init failed %d\n", ret); + return 1; + } + + assert(p.cq_entries < N); + /* submit N SQEs, some should overflow */ + for (i = 0; i < N; i++) { + submit_one_nop(&ring, i); + outstanding++; + } + + for (i = 0; i < LOOPS; i++) { + struct io_uring_cqe *cqes[N]; + + if (io_uring_cq_has_overflow(&ring)) { + /* + * Flush any overflowed CQEs and process those. Actively + * flush these to make sure CQEs arrive in vague order + * of being sent. + */ + ret = io_uring_get_events(&ring); + if (ret != 0) { + fprintf(stderr, + "io_uring_get_events returned %d\n", + ret); + goto err; + } + } else if (!cqe_dropped) { + for (j = 0; j < queued; j++) { + submit_one_nop(&ring, queue[j]); + outstanding++; + } + queued = 0; + } + + /* We have lost some random cqes, stop if no remaining. */ + if (cqe_dropped && outstanding == *ring.cq.koverflow) + break; + + ret = io_uring_wait_cqe(&ring, &cqes[0]); + if (ret == -EBADR) { + cqe_dropped = true; + fprintf(stderr, "CQE dropped\n"); + continue; + } else if (ret != 0) { + fprintf(stderr, "io_uring_wait_cqes failed %d\n", ret); + goto err; + } + cqe_count = 1; + if (batch) { + ret = io_uring_peek_batch_cqe(&ring, &cqes[0], 2); + if (ret < 0) { + fprintf(stderr, + "io_uring_peek_batch_cqe failed %d\n", + ret); + goto err; + } + cqe_count = ret; + } + for (j = 0; j < cqe_count; j++) { + assert(cqes[j]->user_data < N); + ud = cqes[j]->user_data; + completions[ud]++; + assert(queued < QUEUE_LENGTH); + queue[queued++] = (int)ud; + } + io_uring_cq_advance(&ring, cqe_count); + outstanding -= cqe_count; + } + + /* See if there were any drops by flushing the CQ ring *and* overflow */ + do { + struct io_uring_cqe *cqe; + + ret = io_uring_get_events(&ring); + if (ret < 0) { + if (ret == -EBADR) { + fprintf(stderr, "CQE dropped\n"); + cqe_dropped = true; + break; + } + goto err; + } + if (outstanding && !io_uring_cq_ready(&ring)) + ret = io_uring_wait_cqe_timeout(&ring, &cqe, NULL); + + if (ret && ret != -ETIME) { + if (ret == -EBADR) { + fprintf(stderr, "CQE dropped\n"); + cqe_dropped = true; + break; + } + fprintf(stderr, "wait_cqe_timeout = %d\n", ret); + goto err; + } + count = io_uring_cq_ready(&ring); + io_uring_cq_advance(&ring, count); + outstanding -= count; + } while (count); + + io_uring_queue_exit(&ring); + + /* Make sure that completions come back in the same order they were + * sent. If they come back unfairly then this will concentrate on a + * couple of indices. + */ + for (i = 1; !cqe_dropped && i < N; i++) { + if (abs(completions[i] - completions[i - 1]) > 1) { + fprintf(stderr, "bad completion size %d %d\n", + completions[i], completions[i - 1]); + goto err; + } + } + return 0; +err: + io_uring_queue_exit(&ring); + return 1; +} + +int main(int argc, char *argv[]) +{ + const char *fname = ".cq-overflow"; + unsigned iters, drops; + unsigned long usecs; + int ret; + int i; + bool can_defer; + + if (argc > 1) + return T_EXIT_SKIP; + + can_defer = t_probe_defer_taskrun(); + for (i = 0; i < 16; i++) { + bool batch = i & 1; + int mult = (i & 2) ? 1 : 2; + bool poll = i & 4; + bool defer = i & 8; + + if (defer && !can_defer) + continue; + + ret = test_overflow_handling(batch, mult, poll, defer); + if (ret) { + fprintf(stderr, "test_overflow_handling(" + "batch=%d, mult=%d, poll=%d, defer=%d) failed\n", + batch, mult, poll, defer); + goto err; + } + } + + ret = test_overflow(); + if (ret) { + fprintf(stderr, "test_overflow failed\n"); + return ret; + } + + t_create_file(fname, FILE_SIZE); + + vecs = t_create_buffers(BUFFERS, BS); + + iters = 0; + usecs = 1000; + do { + drops = 0; + + if (test_io(fname, usecs, &drops, 0)) { + fprintf(stderr, "test_io nofault failed\n"); + goto err; + } + if (drops) + break; + usecs = (usecs * 12) / 10; + iters++; + } while (iters < 40); + + if (test_io(fname, usecs, &drops, 0)) { + fprintf(stderr, "test_io nofault failed\n"); + goto err; + } + + if (test_io(fname, usecs, &drops, 1)) { + fprintf(stderr, "test_io fault failed\n"); + goto err; + } + + unlink(fname); + return T_EXIT_PASS; +err: + unlink(fname); + return T_EXIT_FAIL; +} |
