From: David Carlier <devnexen@gmail.com>
To: netdev@vger.kernel.org
Cc: sgarzare@redhat.com, bobbyeshleman@gmail.com,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org,
virtualization@lists.linux.dev, linux-kernel@vger.kernel.org,
David Carlier <devnexen@gmail.com>
Subject: [PATCH net-next v2 2/2] vsock/test: cover receive queue hints
Date: Wed, 23 Sep 2026 19:40:08 +0100 [thread overview]
Message-ID: <20260923184008.153541-3-devnexen@gmail.com> (raw)
In-Reply-To: <20260923184008.153541-1-devnexen@gmail.com>
Add io_uring receive tests checking IORING_CQE_F_SOCK_NONEMPTY across a
partial receive, draining while the peer stays connected, EOF, a
nonblocking receive on an empty queue, a zero-length request, and a
multishot receive with provided buffers.
Signed-off-by: David Carlier <devnexen@gmail.com>
---
tools/testing/vsock/util.c | 9 +
tools/testing/vsock/util.h | 1 +
tools/testing/vsock/vsock_uring_test.c | 391 +++++++++++++++++++++++++
3 files changed, 401 insertions(+)
diff --git a/tools/testing/vsock/util.c b/tools/testing/vsock/util.c
index fe316b02a590..299a4e8ecb63 100644
--- a/tools/testing/vsock/util.c
+++ b/tools/testing/vsock/util.c
@@ -483,6 +483,15 @@ void recv_byte(int fd, int expected_ret, int flags)
}
}
+void expect_res(int res, int expected, const char *what)
+{
+ if (res != expected) {
+ fprintf(stderr, "%s: expected %d, got %d\n", what, expected,
+ res);
+ exit(EXIT_FAILURE);
+ }
+}
+
/* Run test cases. The program terminates if a failure occurs. */
void run_tests(const struct test_case *test_cases,
const struct test_opts *opts)
diff --git a/tools/testing/vsock/util.h b/tools/testing/vsock/util.h
index bf633cde82b0..9dabb547021e 100644
--- a/tools/testing/vsock/util.h
+++ b/tools/testing/vsock/util.h
@@ -94,6 +94,7 @@ void send_buf(int fd, const void *buf, size_t len, int flags,
void recv_buf(int fd, void *buf, size_t len, int flags, ssize_t expected_ret);
void send_byte(int fd, int expected_ret, int flags);
void recv_byte(int fd, int expected_ret, int flags);
+void expect_res(int res, int expected, const char *what);
void run_tests(const struct test_case *test_cases,
const struct test_opts *opts);
void list_tests(const struct test_case *test_cases);
diff --git a/tools/testing/vsock/vsock_uring_test.c b/tools/testing/vsock/vsock_uring_test.c
index 5c3078969659..c6cc51db7adc 100644
--- a/tools/testing/vsock/vsock_uring_test.c
+++ b/tools/testing/vsock/vsock_uring_test.c
@@ -13,7 +13,10 @@
#include <liburing.h>
#include <unistd.h>
#include <sys/mman.h>
+#include <sys/ioctl.h>
#include <linux/kernel.h>
+#include <linux/sockios.h>
+#include <errno.h>
#include <error.h>
#include "util.h"
@@ -28,6 +31,10 @@
#define VSOCK_TEST_DATA_MAX_IOV 3
+#define HINT_CHUNK_SIZE 4096
+#define HINT_BUF_GROUP 1
+#define HINT_BUF_ENTRIES 4
+
struct vsock_io_uring_test {
/* Number of valid elements in 'vecs'. */
int vecs_cnt;
@@ -211,6 +218,365 @@ void test_stream_uring_msg_zc_client(const struct test_opts *opts)
vsock_io_uring_client(opts, &test_data_array[i], true);
}
+struct uring_inq_ctx {
+ struct io_uring ring;
+ int fd;
+};
+
+static void inq_server_init(struct uring_inq_ctx *ctx,
+ const struct test_opts *opts)
+{
+ ctx->fd = vsock_stream_accept(VMADDR_CID_ANY, opts->peer_port, NULL);
+ if (ctx->fd < 0) {
+ perror("accept");
+ exit(EXIT_FAILURE);
+ }
+
+ if (io_uring_queue_init(RING_ENTRIES_NUM, &ctx->ring, 0))
+ error(1, errno, "io_uring_queue_init");
+}
+
+static void inq_server_exit(struct uring_inq_ctx *ctx)
+{
+ io_uring_queue_exit(&ctx->ring);
+ close(ctx->fd);
+}
+
+/* Submit a single receive and report both its result and its CQE flags. */
+static int inq_recv(struct uring_inq_ctx *ctx, void *buf, size_t len,
+ int flags, unsigned int *cflags)
+{
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ int res;
+
+ sqe = io_uring_get_sqe(&ctx->ring);
+ io_uring_prep_recv(sqe, ctx->fd, buf, len, flags);
+
+ if (io_uring_submit(&ctx->ring) != 1)
+ error(1, errno, "io_uring_submit");
+
+ if (io_uring_wait_cqe(&ctx->ring, &cqe))
+ error(1, errno, "io_uring_wait_cqe");
+
+ res = cqe->res;
+ *cflags = cqe->flags;
+ io_uring_cqe_seen(&ctx->ring, cqe);
+
+ return res;
+}
+
+static void expect_uring_nonempty(unsigned int cflags, bool expected,
+ const char *what)
+{
+ bool nonempty = !!(cflags & IORING_CQE_F_SOCK_NONEMPTY);
+
+ if (nonempty != expected) {
+ fprintf(stderr, "%s: expected SOCK_NONEMPTY %d, got %d\n",
+ what, expected, nonempty);
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void expect_uring_more(unsigned int cflags, bool expected,
+ const char *what)
+{
+ bool more = !!(cflags & IORING_CQE_F_MORE);
+
+ if (more != expected) {
+ fprintf(stderr, "%s: expected F_MORE %d, got %d\n",
+ what, expected, more);
+ exit(EXIT_FAILURE);
+ }
+}
+
+/* Wait until the whole payload is queued, so the hint is deterministic.
+ * Return false if the test has to be skipped.
+ */
+static bool inq_wait_queued(int fd, int len)
+{
+ if (!vsock_ioctl_int(fd, SIOCINQ, len)) {
+ fprintf(stderr, "Test skipped, SIOCINQ not supported.\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void inq_send_chunks(const struct test_opts *opts, int chunks)
+{
+ char buf[HINT_CHUNK_SIZE];
+ int fd, i;
+
+ fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(buf, 0xa5, sizeof(buf));
+ for (i = 0; i < chunks; i++)
+ send_buf(fd, buf, sizeof(buf), 0, sizeof(buf));
+
+ control_writeln("SENT");
+ control_expectln("DONE");
+ close(fd);
+}
+
+static void test_stream_uring_inq_client(const struct test_opts *opts)
+{
+ inq_send_chunks(opts, 2);
+}
+
+static void test_stream_uring_inq_server(const struct test_opts *opts)
+{
+ char buf[HINT_CHUNK_SIZE];
+ struct uring_inq_ctx ctx;
+ unsigned int cflags;
+ int res;
+
+ inq_server_init(&ctx, opts);
+
+ control_expectln("SENT");
+ if (!inq_wait_queued(ctx.fd, 2 * HINT_CHUNK_SIZE))
+ goto out;
+
+ /* Data remains after this receive, so the flag must be set. */
+ res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags);
+ expect_res(res, HINT_CHUNK_SIZE, "partial receive");
+ expect_uring_nonempty(cflags, true, "partial receive");
+
+ /* This receive drains the queue while the peer stays connected. */
+ res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags);
+ expect_res(res, HINT_CHUNK_SIZE, "draining receive");
+ expect_uring_nonempty(cflags, false, "draining receive");
+
+out:
+ control_writeln("DONE");
+ inq_server_exit(&ctx);
+}
+
+static void test_stream_uring_inq_eof_client(const struct test_opts *opts)
+{
+ char buf[HINT_CHUNK_SIZE];
+ int fd;
+
+ fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(buf, 0x5a, sizeof(buf));
+ send_buf(fd, buf, sizeof(buf), 0, sizeof(buf));
+ control_writeln("SENT");
+
+ control_expectln("DRAINED");
+ close(fd);
+ control_writeln("CLOSED");
+}
+
+static void test_stream_uring_inq_eof_server(const struct test_opts *opts)
+{
+ char buf[HINT_CHUNK_SIZE];
+ struct uring_inq_ctx ctx;
+ unsigned int cflags;
+ int res;
+
+ inq_server_init(&ctx, opts);
+
+ control_expectln("SENT");
+ if (!inq_wait_queued(ctx.fd, HINT_CHUNK_SIZE)) {
+ control_writeln("DRAINED");
+ control_expectln("CLOSED");
+ goto out;
+ }
+
+ res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags);
+ expect_res(res, HINT_CHUNK_SIZE, "drain before EOF");
+ expect_uring_nonempty(cflags, false, "drain before EOF");
+
+ control_writeln("DRAINED");
+ control_expectln("CLOSED");
+
+ /* The queue is empty and the peer is gone. The hint stays non-zero
+ * so that this receive happens and reports EOF, as TCP does after a
+ * FIN.
+ */
+ res = inq_recv(&ctx, buf, sizeof(buf), 0, &cflags);
+ expect_res(res, 0, "receive at EOF");
+ expect_uring_nonempty(cflags, true, "receive at EOF");
+
+out:
+ inq_server_exit(&ctx);
+}
+
+static void test_stream_uring_inq_empty_client(const struct test_opts *opts)
+{
+ int fd;
+
+ fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ control_writeln("READY");
+ control_expectln("DONE");
+ close(fd);
+}
+
+static void test_stream_uring_inq_empty_server(const struct test_opts *opts)
+{
+ char buf[HINT_CHUNK_SIZE];
+ struct uring_inq_ctx ctx;
+ unsigned int cflags;
+ int res;
+
+ inq_server_init(&ctx, opts);
+
+ control_expectln("READY");
+
+ /* A failed receive must not leave a stale positive hint. */
+ res = inq_recv(&ctx, buf, sizeof(buf), MSG_DONTWAIT, &cflags);
+ expect_res(res, -EAGAIN, "empty nonblocking receive");
+ expect_uring_nonempty(cflags, false, "empty nonblocking receive");
+
+ control_writeln("DONE");
+ inq_server_exit(&ctx);
+}
+
+static void test_stream_uring_inq_zerolen_client(const struct test_opts *opts)
+{
+ inq_send_chunks(opts, 1);
+}
+
+static void test_stream_uring_inq_zerolen_server(const struct test_opts *opts)
+{
+ char buf[HINT_CHUNK_SIZE];
+ struct uring_inq_ctx ctx;
+ unsigned int cflags;
+ int res;
+
+ inq_server_init(&ctx, opts);
+
+ control_expectln("SENT");
+ if (!inq_wait_queued(ctx.fd, HINT_CHUNK_SIZE))
+ goto out;
+
+ /* A zero-length request is not an error and still describes the
+ * queue behind it.
+ */
+ res = inq_recv(&ctx, buf, 0, 0, &cflags);
+ expect_res(res, 0, "zero-length receive");
+ expect_uring_nonempty(cflags, true, "zero-length receive");
+
+out:
+ control_writeln("DONE");
+ inq_server_exit(&ctx);
+}
+
+static void test_stream_uring_inq_mshot_client(const struct test_opts *opts)
+{
+ char buf[HINT_CHUNK_SIZE];
+ int fd, i;
+
+ fd = vsock_stream_connect(opts->peer_cid, opts->peer_port);
+ if (fd < 0) {
+ perror("connect");
+ exit(EXIT_FAILURE);
+ }
+
+ memset(buf, 0x3c, sizeof(buf));
+ for (i = 0; i < 2; i++)
+ send_buf(fd, buf, sizeof(buf), 0, sizeof(buf));
+ control_writeln("SENT");
+
+ control_expectln("DRAINED");
+ close(fd);
+ control_writeln("CLOSED");
+
+ control_expectln("DONE");
+}
+
+static void test_stream_uring_inq_mshot_server(const struct test_opts *opts)
+{
+ static char bufs[HINT_BUF_ENTRIES][HINT_CHUNK_SIZE];
+ struct io_uring_buf_ring *br;
+ struct uring_inq_ctx ctx;
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ int i, ret;
+
+ inq_server_init(&ctx, opts);
+
+ br = io_uring_setup_buf_ring(&ctx.ring, HINT_BUF_ENTRIES,
+ HINT_BUF_GROUP, 0, &ret);
+ if (!br) {
+ fprintf(stderr, "io_uring_setup_buf_ring: %d\n", ret);
+ exit(EXIT_FAILURE);
+ }
+
+ for (i = 0; i < HINT_BUF_ENTRIES; i++)
+ io_uring_buf_ring_add(br, bufs[i], HINT_CHUNK_SIZE, i,
+ io_uring_buf_ring_mask(HINT_BUF_ENTRIES),
+ i);
+ io_uring_buf_ring_advance(br, HINT_BUF_ENTRIES);
+
+ control_expectln("SENT");
+ if (!inq_wait_queued(ctx.fd, 2 * HINT_CHUNK_SIZE)) {
+ control_writeln("DRAINED");
+ control_expectln("CLOSED");
+ goto out;
+ }
+
+ /* Arm only once both chunks are queued, so every completion knows
+ * what is left behind it.
+ */
+ sqe = io_uring_get_sqe(&ctx.ring);
+ io_uring_prep_recv_multishot(sqe, ctx.fd, NULL, 0, 0);
+ sqe->flags |= IOSQE_BUFFER_SELECT;
+ sqe->buf_group = HINT_BUF_GROUP;
+
+ if (io_uring_submit(&ctx.ring) != 1)
+ error(1, errno, "io_uring_submit");
+
+ /* A buffer holds one chunk, so the other one is still queued. */
+ if (io_uring_wait_cqe(&ctx.ring, &cqe))
+ error(1, errno, "io_uring_wait_cqe");
+
+ expect_res(cqe->res, HINT_CHUNK_SIZE, "multishot first chunk");
+ expect_uring_nonempty(cqe->flags, true, "multishot first chunk");
+ expect_uring_more(cqe->flags, true, "multishot first chunk");
+ io_uring_cqe_seen(&ctx.ring, cqe);
+
+ /* This completion drains the queue and keeps the request armed. */
+ if (io_uring_wait_cqe(&ctx.ring, &cqe))
+ error(1, errno, "io_uring_wait_cqe");
+
+ expect_res(cqe->res, HINT_CHUNK_SIZE, "multishot second chunk");
+ expect_uring_nonempty(cqe->flags, false, "multishot second chunk");
+ expect_uring_more(cqe->flags, true, "multishot second chunk");
+ io_uring_cqe_seen(&ctx.ring, cqe);
+
+ control_writeln("DRAINED");
+ control_expectln("CLOSED");
+
+ /* EOF ends multishot regardless of the hint. */
+ if (io_uring_wait_cqe(&ctx.ring, &cqe))
+ error(1, errno, "io_uring_wait_cqe");
+
+ expect_res(cqe->res, 0, "multishot EOF");
+ expect_uring_more(cqe->flags, false, "multishot EOF");
+ io_uring_cqe_seen(&ctx.ring, cqe);
+
+out:
+ control_writeln("DONE");
+ io_uring_free_buf_ring(&ctx.ring, br, HINT_BUF_ENTRIES,
+ HINT_BUF_GROUP);
+ inq_server_exit(&ctx);
+}
+
static struct test_case test_cases[] = {
{
.name = "SOCK_STREAM io_uring test",
@@ -222,6 +588,31 @@ static struct test_case test_cases[] = {
.run_server = test_stream_uring_msg_zc_server,
.run_client = test_stream_uring_msg_zc_client,
},
+ {
+ .name = "SOCK_STREAM io_uring receive queue hint",
+ .run_server = test_stream_uring_inq_server,
+ .run_client = test_stream_uring_inq_client,
+ },
+ {
+ .name = "SOCK_STREAM io_uring receive hint at EOF",
+ .run_server = test_stream_uring_inq_eof_server,
+ .run_client = test_stream_uring_inq_eof_client,
+ },
+ {
+ .name = "SOCK_STREAM io_uring receive hint on empty queue",
+ .run_server = test_stream_uring_inq_empty_server,
+ .run_client = test_stream_uring_inq_empty_client,
+ },
+ {
+ .name = "SOCK_STREAM io_uring receive hint zero-length",
+ .run_server = test_stream_uring_inq_zerolen_server,
+ .run_client = test_stream_uring_inq_zerolen_client,
+ },
+ {
+ .name = "SOCK_STREAM io_uring multishot receive hint",
+ .run_server = test_stream_uring_inq_mshot_server,
+ .run_client = test_stream_uring_inq_mshot_client,
+ },
{},
};
--
2.55.0
next prev parent reply other threads:[~2026-09-23 18:40 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 18:40 [PATCH net-next v2 0/2] vsock: receive queue hint for io_uring David Carlier
2026-09-23 18:40 ` [PATCH net-next v2 1/2] vsock: report pending receive data to io_uring David Carlier
2026-09-23 18:46 ` sashiko-bot
2026-09-28 15:00 ` Stefano Garzarella
2026-09-23 18:40 ` David Carlier [this message]
2026-09-23 18:46 ` [PATCH net-next v2 2/2] vsock/test: cover receive queue hints sashiko-bot
2026-09-23 19:29 ` Bobby Eshleman
2026-09-28 15:00 ` Stefano Garzarella
2026-09-29 0:30 ` [PATCH net-next v2 0/2] vsock: receive queue hint for io_uring patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260923184008.153541-3-devnexen@gmail.com \
--to=devnexen@gmail.com \
--cc=bobbyeshleman@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=sgarzare@redhat.com \
--cc=virtualization@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.