#include "../config-host.h" /* SPDX-License-Identifier: MIT */ /* * Description: stress test multishot recv with buffer ring exhaustion and * refill. Verifies that multishot recv handles the case where * the buffer ring runs dry during active receives, and that * data is correctly received after refilling buffers. */ #include #include #include #include #include #include #include #include #include #include "liburing.h" #include "helpers.h" #define BGID 1 #define BUF_SIZE 128 /* Intentionally small to force exhaustion */ #define NR_BUFS 4 #define NR_SENDS 4096 #define SEND_SIZE 64 struct thread_data { pthread_barrier_t connect_barrier; int port; }; static void provide_buffers(struct io_uring_buf_ring *br, void *base, int nr_bufs, int buf_size, int start_bid) { int i; for (i = 0; i < nr_bufs; i++) { void *addr = base + i * buf_size; io_uring_buf_ring_add(br, addr, buf_size, start_bid + i, io_uring_buf_ring_mask(nr_bufs), i); } io_uring_buf_ring_advance(br, nr_bufs); } static void *send_thread(void *arg) { struct thread_data *td = arg; struct sockaddr_in saddr; char buf[SEND_SIZE]; int fd, ret, i; memset(buf, 0xaa, sizeof(buf)); fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return (void *)(intptr_t)1; } memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(td->port); inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr); pthread_barrier_wait(&td->connect_barrier); ret = connect(fd, (struct sockaddr *)&saddr, sizeof(saddr)); if (ret < 0) { perror("connect"); close(fd); return (void *)(intptr_t)1; } /* Send more data than we have buffers for */ for (i = 0; i < NR_SENDS; i++) { /* Fill pattern: iteration number */ memset(buf, i & 0xff, sizeof(buf)); ret = send(fd, buf, sizeof(buf), 0); if (ret < 0) { if (errno == EPIPE) break; perror("send"); close(fd); return (void *)(intptr_t)1; } /* Small delay to spread sends over time */ if (i % 8 == 7) usleep(1000); } close(fd); return NULL; } static int test_recv_drain_refill(void) { struct io_uring ring; struct io_uring_buf_ring *br; struct io_uring_sqe *sqe; struct io_uring_cqe *cqe; struct thread_data td; struct sockaddr_in saddr; pthread_t thread; void *buf_base; int sockfd, connfd, ret, val; int total_recv = 0; socklen_t socklen; int port; ret = io_uring_queue_init(32, &ring, 0); if (ret) { fprintf(stderr, "ring setup: %d\n", ret); return T_EXIT_FAIL; } /* Allocate buffer ring */ br = io_uring_setup_buf_ring(&ring, NR_BUFS, BGID, 0, &ret); if (!br) { if (ret == -EINVAL || ret == -ENOENT) { io_uring_queue_exit(&ring); return T_EXIT_SKIP; } fprintf(stderr, "buf ring setup: %d\n", ret); io_uring_queue_exit(&ring); return T_EXIT_FAIL; } buf_base = malloc(NR_BUFS * BUF_SIZE); if (!buf_base) { fprintf(stderr, "malloc\n"); goto err; } provide_buffers(br, buf_base, NR_BUFS, BUF_SIZE, 0); /* Setup listening socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); goto err; } val = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); memset(&saddr, 0, sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_addr.s_addr = htonl(INADDR_ANY); saddr.sin_port = 0; ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)); if (ret < 0) { perror("bind"); close(sockfd); goto err; } socklen = sizeof(saddr); getsockname(sockfd, (struct sockaddr *)&saddr, &socklen); port = ntohs(saddr.sin_port); ret = listen(sockfd, 1); if (ret < 0) { perror("listen"); close(sockfd); goto err; } /* Start sender thread */ td.port = port; pthread_barrier_init(&td.connect_barrier, NULL, 2); pthread_create(&thread, NULL, send_thread, &td); pthread_barrier_wait(&td.connect_barrier); socklen = sizeof(saddr); connfd = accept(sockfd, (struct sockaddr *)&saddr, &socklen); if (connfd < 0) { perror("accept"); close(sockfd); goto err; } /* Submit multishot recv */ sqe = io_uring_get_sqe(&ring); io_uring_prep_recv_multishot(sqe, connfd, NULL, 0, 0); sqe->buf_group = BGID; sqe->flags |= IOSQE_BUFFER_SELECT; sqe->user_data = 1; ret = io_uring_submit(&ring); if (ret != 1) { fprintf(stderr, "submit recv: %d\n", ret); goto err_conn; } /* * Process CQEs. The multishot will eventually run out of buffers. * When it does (no IORING_CQE_F_MORE), we refill and re-arm. */ while (1) { struct __kernel_timespec ts = { .tv_sec = 2 }; ret = io_uring_wait_cqe_timeout(&ring, &cqe, &ts); if (ret == -ETIME) break; if (ret) { fprintf(stderr, "wait cqe: %d\n", ret); goto err_conn; } if (cqe->res == -ENOBUFS) { /* Buffer ring exhausted - refill and re-arm */ io_uring_cqe_seen(&ring, cqe); provide_buffers(br, buf_base, NR_BUFS, BUF_SIZE, 0); sqe = io_uring_get_sqe(&ring); io_uring_prep_recv_multishot(sqe, connfd, NULL, 0, 0); sqe->buf_group = BGID; sqe->flags |= IOSQE_BUFFER_SELECT; sqe->user_data = 1; ret = io_uring_submit(&ring); if (ret != 1) { fprintf(stderr, "rearm submit: %d\n", ret); goto err_conn; } continue; } if (cqe->res == 0) { /* EOF */ io_uring_cqe_seen(&ring, cqe); break; } if (cqe->res < 0) { fprintf(stderr, "recv error: %d\n", cqe->res); io_uring_cqe_seen(&ring, cqe); goto err_conn; } total_recv += cqe->res; /* Return buffer if we got one */ if (cqe->flags & IORING_CQE_F_BUFFER) { int bid = cqe->flags >> IORING_CQE_BUFFER_SHIFT; void *addr = buf_base + bid * BUF_SIZE; io_uring_buf_ring_add(br, addr, BUF_SIZE, bid, io_uring_buf_ring_mask(NR_BUFS), 0); io_uring_buf_ring_advance(br, 1); } /* If no F_MORE, multishot terminated - re-arm */ if (!(cqe->flags & IORING_CQE_F_MORE)) { io_uring_cqe_seen(&ring, cqe); sqe = io_uring_get_sqe(&ring); io_uring_prep_recv_multishot(sqe, connfd, NULL, 0, 0); sqe->buf_group = BGID; sqe->flags |= IOSQE_BUFFER_SELECT; sqe->user_data = 1; ret = io_uring_submit(&ring); if (ret != 1) { fprintf(stderr, "rearm submit: %d\n", ret); goto err_conn; } continue; } io_uring_cqe_seen(&ring, cqe); } pthread_join(thread, NULL); if (total_recv != NR_SENDS * SEND_SIZE) { fprintf(stderr, "recv %d bytes, expected %d\n", total_recv, NR_SENDS * SEND_SIZE); goto err_conn; } close(connfd); close(sockfd); free(buf_base); io_uring_free_buf_ring(&ring, br, NR_BUFS, BGID); io_uring_queue_exit(&ring); return T_EXIT_PASS; err_conn: close(connfd); close(sockfd); err: free(buf_base); io_uring_queue_exit(&ring); return T_EXIT_FAIL; } int main(int argc, char *argv[]) { int ret; if (argc > 1) return T_EXIT_SKIP; ret = test_recv_drain_refill(); if (ret == T_EXIT_SKIP) { printf("Buffer rings not supported, skipping\n"); return T_EXIT_SKIP; } if (ret == T_EXIT_FAIL) { fprintf(stderr, "test_recv_drain_refill failed\n"); return T_EXIT_FAIL; } return T_EXIT_PASS; }