#include "../config-host.h" /* SPDX-License-Identifier: MIT */ /* * Description: verify that a single buffer ring shared across multiple * concurrent multishot recv operations works correctly. * Tests buffer distribution, data integrity, and exhaustion * handling with refill across multiple streams. */ #include #include #include #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" #define NR_STREAMS 4 #define BGID 1 #define BUF_SIZE 256 #define NR_BUFS_BASIC 32 #define NR_BUFS_SMALL 8 #define NR_SENDS 32 #define NR_SENDS_EXHAUST 64 #define NR_SENDS_BIG 128 #define NR_SENDS_SMALL 8 #define SEND_SIZE 128 #define UDATA_STREAM(id) (((__u64)(id) + 1) << 32) #define UDATA_GET_STREAM(ud) ((int)(((ud) >> 32) - 1)) struct stream { int send_fd; int recv_fd; int nr_sends; int bytes_sent; int bytes_recv; }; struct sender_ctx { pthread_barrier_t *barrier; struct stream *stream; int stream_id; }; struct test_ctx { struct io_uring ring; struct io_uring_buf_ring *br; void *buf_base; int nr_bufs; struct stream streams[NR_STREAMS]; }; static int no_buf_ring; static int no_mshot_recv; static int setup_streams(struct test_ctx *ctx) { int i; for (i = 0; i < NR_STREAMS; i++) { int fds[2]; int ret; ret = t_create_socket_pair(fds, true); if (ret) { fprintf(stderr, "socket pair %d: %d\n", i, ret); /* cleanup already created pairs */ while (--i >= 0) { close(ctx->streams[i].recv_fd); close(ctx->streams[i].send_fd); } return -1; } ctx->streams[i].recv_fd = fds[0]; ctx->streams[i].send_fd = fds[1]; ctx->streams[i].bytes_sent = 0; ctx->streams[i].bytes_recv = 0; } return 0; } static void close_streams(struct test_ctx *ctx) { int i; for (i = 0; i < NR_STREAMS; i++) { close(ctx->streams[i].recv_fd); close(ctx->streams[i].send_fd); } } static int setup_buf_ring(struct test_ctx *ctx, int nr_bufs) { int ret, i; ctx->nr_bufs = nr_bufs; ctx->buf_base = malloc(nr_bufs * BUF_SIZE); if (!ctx->buf_base) return -ENOMEM; ctx->br = io_uring_setup_buf_ring(&ctx->ring, nr_bufs, BGID, 0, &ret); if (!ctx->br) { free(ctx->buf_base); ctx->buf_base = NULL; if (ret == -EINVAL || ret == -ENOENT) return ret; fprintf(stderr, "buf ring setup: %d\n", ret); return ret; } for (i = 0; i < nr_bufs; i++) { io_uring_buf_ring_add(ctx->br, ctx->buf_base + i * BUF_SIZE, BUF_SIZE, i, io_uring_buf_ring_mask(nr_bufs), i); } io_uring_buf_ring_advance(ctx->br, nr_bufs); return 0; } static void cleanup_buf_ring(struct test_ctx *ctx) { if (ctx->br) { io_uring_free_buf_ring(&ctx->ring, ctx->br, ctx->nr_bufs, BGID); ctx->br = NULL; } free(ctx->buf_base); ctx->buf_base = NULL; } static void recycle_buffer(struct test_ctx *ctx, int bid) { io_uring_buf_ring_add(ctx->br, ctx->buf_base + bid * BUF_SIZE, BUF_SIZE, bid, io_uring_buf_ring_mask(ctx->nr_bufs), 0); io_uring_buf_ring_advance(ctx->br, 1); } static void arm_recv(struct io_uring *ring, int fd, int stream_id) { struct io_uring_sqe *sqe; sqe = io_uring_get_sqe(ring); io_uring_prep_recv_multishot(sqe, fd, NULL, 0, 0); sqe->buf_group = BGID; sqe->flags |= IOSQE_BUFFER_SELECT; io_uring_sqe_set_data64(sqe, UDATA_STREAM(stream_id)); } static void *sender_fn(void *data) { struct sender_ctx *sc = data; struct stream *s = sc->stream; char buf[SEND_SIZE]; int i; memset(buf, sc->stream_id & 0xff, sizeof(buf)); pthread_barrier_wait(sc->barrier); for (i = 0; i < s->nr_sends; i++) { int ret = send(s->send_fd, buf, sizeof(buf), 0); if (ret < 0) break; s->bytes_sent += ret; } shutdown(s->send_fd, SHUT_WR); return NULL; } /* * Run a shared buffer ring test with the given parameters. */ static int run_shared_test(int nr_bufs, int *sends_per_stream) { struct test_ctx ctx = { }; struct sender_ctx senders[NR_STREAMS]; pthread_t threads[NR_STREAMS]; pthread_barrier_t barrier; struct __kernel_timespec ts = { .tv_sec = 5 }; int ret, i, done = 0; ret = io_uring_queue_init(128, &ctx.ring, 0); if (ret) { fprintf(stderr, "ring setup: %d\n", ret); return T_EXIT_FAIL; } ret = setup_streams(&ctx); if (ret) { io_uring_queue_exit(&ctx.ring); return T_EXIT_FAIL; } ret = setup_buf_ring(&ctx, nr_bufs); if (ret == -EINVAL || ret == -ENOENT) { no_buf_ring = 1; close_streams(&ctx); io_uring_queue_exit(&ctx.ring); return T_EXIT_SKIP; } if (ret) { close_streams(&ctx); io_uring_queue_exit(&ctx.ring); return T_EXIT_FAIL; } for (i = 0; i < NR_STREAMS; i++) ctx.streams[i].nr_sends = sends_per_stream[i]; pthread_barrier_init(&barrier, NULL, NR_STREAMS + 1); for (i = 0; i < NR_STREAMS; i++) { senders[i].barrier = &barrier; senders[i].stream = &ctx.streams[i]; senders[i].stream_id = i; pthread_create(&threads[i], NULL, sender_fn, &senders[i]); } /* Arm multishot recv on all streams */ for (i = 0; i < NR_STREAMS; i++) arm_recv(&ctx.ring, ctx.streams[i].recv_fd, i); ret = io_uring_submit(&ctx.ring); if (ret != NR_STREAMS) { fprintf(stderr, "submit: %d\n", ret); goto err; } /* Release all senders */ pthread_barrier_wait(&barrier); while (done < NR_STREAMS) { struct io_uring_cqe *cqe; int sid, bid; ret = io_uring_wait_cqe_timeout(&ctx.ring, &cqe, &ts); if (ret == -ETIME) { fprintf(stderr, "timeout: %d streams done\n", done); goto err; } if (ret) { fprintf(stderr, "wait: %d\n", ret); goto err; } sid = UDATA_GET_STREAM(cqe->user_data); if (sid < 0 || sid >= NR_STREAMS) { fprintf(stderr, "bad user_data: 0x%llx\n", (unsigned long long)cqe->user_data); io_uring_cqe_seen(&ctx.ring, cqe); goto err; } /* Multishot recv not supported */ if (cqe->res == -EINVAL) { no_mshot_recv = 1; io_uring_cqe_seen(&ctx.ring, cqe); goto skip; } /* EOF */ if (cqe->res == 0) { done++; io_uring_cqe_seen(&ctx.ring, cqe); continue; } /* Buffer exhaustion */ if (cqe->res == -ENOBUFS) { io_uring_cqe_seen(&ctx.ring, cqe); arm_recv(&ctx.ring, ctx.streams[sid].recv_fd, sid); io_uring_submit(&ctx.ring); continue; } if (cqe->res < 0) { fprintf(stderr, "recv stream %d: %d\n", sid, cqe->res); io_uring_cqe_seen(&ctx.ring, cqe); goto err; } if (!(cqe->flags & IORING_CQE_F_BUFFER)) { fprintf(stderr, "no buffer flag stream %d\n", sid); io_uring_cqe_seen(&ctx.ring, cqe); goto err; } bid = cqe->flags >> IORING_CQE_BUFFER_SHIFT; if (bid >= nr_bufs) { fprintf(stderr, "bad bid %d (max %d)\n", bid, nr_bufs); io_uring_cqe_seen(&ctx.ring, cqe); goto err; } /* Verify data integrity */ { unsigned char *data = ctx.buf_base + bid * BUF_SIZE; int j; for (j = 0; j < cqe->res; j++) { if (data[j] != (unsigned char)(sid & 0xff)) { fprintf(stderr, "data mismatch stream %d byte %d: " "got 0x%x expected 0x%x\n", sid, j, data[j], (unsigned char)(sid & 0xff)); io_uring_cqe_seen(&ctx.ring, cqe); goto err; } } } ctx.streams[sid].bytes_recv += cqe->res; recycle_buffer(&ctx, bid); if (!(cqe->flags & IORING_CQE_F_MORE)) { io_uring_cqe_seen(&ctx.ring, cqe); arm_recv(&ctx.ring, ctx.streams[sid].recv_fd, sid); io_uring_submit(&ctx.ring); continue; } io_uring_cqe_seen(&ctx.ring, cqe); } for (i = 0; i < NR_STREAMS; i++) pthread_join(threads[i], NULL); /* Verify all bytes received */ for (i = 0; i < NR_STREAMS; i++) { if (ctx.streams[i].bytes_recv != ctx.streams[i].bytes_sent) { fprintf(stderr, "stream %d: recv %d bytes, sent %d bytes\n", i, ctx.streams[i].bytes_recv, ctx.streams[i].bytes_sent); goto cleanup_fail; } } cleanup_buf_ring(&ctx); close_streams(&ctx); io_uring_queue_exit(&ctx.ring); return T_EXIT_PASS; skip: for (i = 0; i < NR_STREAMS; i++) pthread_join(threads[i], NULL); cleanup_buf_ring(&ctx); close_streams(&ctx); io_uring_queue_exit(&ctx.ring); return T_EXIT_SKIP; err: for (i = 0; i < NR_STREAMS; i++) pthread_join(threads[i], NULL); cleanup_fail: cleanup_buf_ring(&ctx); close_streams(&ctx); io_uring_queue_exit(&ctx.ring); return T_EXIT_FAIL; } /* * Basic test: 4 streams sharing 32 buffers, moderate load */ static int test_shared_basic(void) { int sends[NR_STREAMS]; int i; for (i = 0; i < NR_STREAMS; i++) sends[i] = NR_SENDS; return run_shared_test(NR_BUFS_BASIC, sends); } /* * Exhaustion test: 4 streams sharing only 8 buffers under heavy load */ static int test_shared_exhaust(void) { int sends[NR_STREAMS]; int i; if (no_buf_ring || no_mshot_recv) return T_EXIT_SKIP; for (i = 0; i < NR_STREAMS; i++) sends[i] = NR_SENDS_EXHAUST; return run_shared_test(NR_BUFS_SMALL, sends); } /* * Uneven test: one heavy stream (128 sends), three light streams (8 sends) */ static int test_shared_uneven(void) { int sends[NR_STREAMS]; int i; if (no_buf_ring || no_mshot_recv) return T_EXIT_SKIP; sends[0] = NR_SENDS_BIG; for (i = 1; i < NR_STREAMS; i++) sends[i] = NR_SENDS_SMALL; return run_shared_test(NR_BUFS_BASIC, sends); } int main(int argc, char *argv[]) { int ret; if (argc > 1) return T_EXIT_SKIP; ret = test_shared_basic(); if (ret == T_EXIT_SKIP) { printf("Buffer rings or multishot recv not supported, skipping\n"); return T_EXIT_SKIP; } if (ret == T_EXIT_FAIL) { fprintf(stderr, "test_shared_basic failed\n"); return T_EXIT_FAIL; } ret = test_shared_exhaust(); if (ret == T_EXIT_FAIL) { fprintf(stderr, "test_shared_exhaust failed\n"); return T_EXIT_FAIL; } ret = test_shared_uneven(); if (ret == T_EXIT_FAIL) { fprintf(stderr, "test_shared_uneven failed\n"); return T_EXIT_FAIL; } return T_EXIT_PASS; }