git: fe9bf4b9f580 - main - tests/unix_stream: improve writable test

From: Gleb Smirnoff <glebius_at_FreeBSD.org>
Date: Wed, 30 Apr 2025 06:01:13 UTC
The branch main has been updated by glebius:

URL: https://cgit.FreeBSD.org/src/commit/?id=fe9bf4b9f580d718c5fc936ccb2c9feb225b3f22

commit fe9bf4b9f580d718c5fc936ccb2c9feb225b3f22
Author:     Gleb Smirnoff <glebius@FreeBSD.org>
AuthorDate: 2025-04-30 05:31:41 +0000
Commit:     Gleb Smirnoff <glebius@FreeBSD.org>
CommitDate: 2025-04-30 05:31:41 +0000

    tests/unix_stream: improve writable test
    
    In addition with non-blocking checks on writability, add blocking once
    with help of pthread(3).
---
 tests/sys/kern/Makefile      |   1 +
 tests/sys/kern/unix_stream.c | 165 +++++++++++++++++++++++++++++++++++++------
 2 files changed, 144 insertions(+), 22 deletions(-)

diff --git a/tests/sys/kern/Makefile b/tests/sys/kern/Makefile
index 900c9a5b3bbe..0dc7fcbdd8d7 100644
--- a/tests/sys/kern/Makefile
+++ b/tests/sys/kern/Makefile
@@ -92,6 +92,7 @@ LIBADD.sendfile_helper+=		pthread
 LIBADD.fdgrowtable_test+=		util pthread kvm procstat
 LIBADD.sigwait+=			rt
 LIBADD.ktrace_test+=			sysdecode
+LIBADD.unix_stream+=			pthread
 
 NETBSD_ATF_TESTS_C+=	lockf_test
 NETBSD_ATF_TESTS_C+=	mqueue_test
diff --git a/tests/sys/kern/unix_stream.c b/tests/sys/kern/unix_stream.c
index d93bbeff4e41..7aedf462dcce 100644
--- a/tests/sys/kern/unix_stream.c
+++ b/tests/sys/kern/unix_stream.c
@@ -35,6 +35,8 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <poll.h>
+#include <pthread.h>
+#include <pthread_np.h>
 
 #include <atf-c.h>
 
@@ -100,47 +102,73 @@ ATF_TC_BODY(send_0, tc)
 }
 
 static void
-check_writable(int fd, int expect)
+check_writable_select(int fd, int expect, bool timeout)
 {
 	fd_set wrfds;
-	struct pollfd pfd[1];
-	struct kevent kev;
-	int nfds, kq;
+	int nfds;
 
 	FD_ZERO(&wrfds);
 	FD_SET(fd, &wrfds);
-	nfds = select(fd + 1, NULL, &wrfds, NULL,
-	    &(struct timeval){.tv_usec = 1000});
+	nfds = select(fd + 1, NULL, &wrfds, NULL, timeout ?
+	    &(struct timeval){.tv_usec = 1000} : NULL);
 	ATF_REQUIRE_MSG(nfds == expect,
 	    "select() returns %d errno %d", nfds, errno);
+}
+
+static void
+check_writable_poll(int fd, int expect, bool timeout)
+{
+	struct pollfd pfd[1];
+	int nfds;
 
 	pfd[0] = (struct pollfd){
 		.fd = fd,
 		.events = POLLOUT | POLLWRNORM,
 	};
-	nfds = poll(pfd, 1, 1);
+	nfds = poll(pfd, 1, timeout ? 1 : INFTIM);
 	ATF_REQUIRE_MSG(nfds == expect,
 	    "poll() returns %d errno %d", nfds, errno);
+}
+
+static void
+check_writable_kevent(int fd, int expect, bool timeout)
+{
+	struct kevent kev;
+	int nfds, kq;
 
 	ATF_REQUIRE(kq = kqueue());
 	EV_SET(&kev, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL);
-	ATF_REQUIRE(kevent(kq, &kev, 1, NULL, 0, NULL) == 0);
-	nfds = kevent(kq, NULL, 0, &kev, 1,
-	    &(struct timespec){.tv_nsec = 1000000});
+	nfds = kevent(kq, &kev, 1, NULL, 0, NULL);
+	ATF_REQUIRE_MSG(nfds == 0,
+	    "kevent() returns %d errno %d", nfds, errno);
+	nfds = kevent(kq, NULL, 0, &kev, 1, timeout ?
+	    &(struct timespec){.tv_nsec = 1000000} : NULL);
 	ATF_REQUIRE_MSG(nfds == expect,
-		"kevent() returns %d errno %d", nfds, errno);
+	    "kevent() returns %d errno %d", nfds, errno);
 	close(kq);
 }
 
-/*
- * Make sure that a full socket is not reported as writable by event APIs.
- */
-ATF_TC_WITHOUT_HEAD(full_not_writable);
-ATF_TC_BODY(full_not_writable, tc)
+typedef void check_writable_func_t(int, int, bool);
+struct check_writable_ctx {
+	check_writable_func_t	*method;
+	int			fd;
+};
+
+static void *
+check_writable_blocking_thread(void *arg)
+{
+	struct check_writable_ctx *ctx = arg;
+
+	ctx->method(ctx->fd, 1, false);
+
+	return (NULL);
+}
+
+static void
+full_socketpair(int *sv)
 {
 	void *buf;
 	u_long sendspace;
-	int sv[2];
 
 	sendspace = getsendspace();
 	ATF_REQUIRE((buf = malloc(sendspace)) != NULL);
@@ -149,24 +177,117 @@ ATF_TC_BODY(full_not_writable, tc)
 	do {} while (send(sv[0], buf, sendspace, 0) == (ssize_t)sendspace);
 	ATF_REQUIRE(errno == EAGAIN);
 	ATF_REQUIRE(fcntl(sv[0], F_SETFL, 0) != -1);
+	free(buf);
+}
+
+static void
+full_writability_check(int *sv, check_writable_func_t method)
+{
+	struct check_writable_ctx ctx = {
+		.method = method,
+		.fd = sv[0],
+	};
+	pthread_t thr;
+	void *buf;
+	u_long space;
+
+	space = getsendspace() / 2;
+	ATF_REQUIRE((buf = malloc(space)) != NULL);
 
-	check_writable(sv[0], 0);
+	/* First check with timeout, expecting 0 fds returned. */
+	method(sv[0], 0, true);
 
-	/* Read some data and re-check. */
-	ATF_REQUIRE(read(sv[1], buf, sendspace / 2) == (ssize_t)sendspace / 2);
+	/* Launch blocking thread. */
+	ATF_REQUIRE(pthread_create(&thr, NULL, check_writable_blocking_thread,
+	    &ctx) == 0);
 
-	check_writable(sv[0], 1);
+	/* Sleep a bit to make sure that thread is put to sleep. */
+	usleep(10000);
+	ATF_REQUIRE(pthread_peekjoin_np(thr, NULL) == EBUSY);
 
+	/* Read some data and re-check, the fd is expected to be returned. */
+	ATF_REQUIRE(read(sv[1], buf, space) == (ssize_t)space);
+
+	method(sv[0], 1, true);
+
+	/* Now check that thread was successfully woken up and exited. */
+	ATF_REQUIRE(pthread_join(thr, NULL) == 0);
+
+	close(sv[0]);
+	close(sv[1]);
 	free(buf);
+}
+
+/*
+ * Make sure that a full socket is not reported as writable by event APIs.
+ */
+ATF_TC_WITHOUT_HEAD(full_writability_select);
+ATF_TC_BODY(full_writability_select, tc)
+{
+	int sv[2];
+
+	full_socketpair(sv);
+	full_writability_check(sv, check_writable_select);
+	close(sv[0]);
+	close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(full_writability_poll);
+ATF_TC_BODY(full_writability_poll, tc)
+{
+	int sv[2];
+
+	full_socketpair(sv);
+	full_writability_check(sv, check_writable_poll);
+	close(sv[0]);
+	close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(full_writability_kevent);
+ATF_TC_BODY(full_writability_kevent, tc)
+{
+	int sv[2];
+
+	full_socketpair(sv);
+	full_writability_check(sv, check_writable_kevent);
+	close(sv[0]);
+	close(sv[1]);
+}
+
+ATF_TC_WITHOUT_HEAD(connected_writability);
+ATF_TC_BODY(connected_writability, tc)
+{
+	int sv[2];
+
+	do_socketpair(sv);
+	check_writable_select(sv[0], 1, true);
+	check_writable_poll(sv[0], 1, true);
+	check_writable_kevent(sv[0], 1, true);
 	close(sv[0]);
 	close(sv[1]);
 }
 
+ATF_TC_WITHOUT_HEAD(unconnected_writability);
+ATF_TC_BODY(unconnected_writability, tc)
+{
+	int s;
+
+	ATF_REQUIRE((s = socket(PF_LOCAL, SOCK_STREAM, 0)) > 0);
+	check_writable_select(s, 0, true);
+	check_writable_poll(s, 0, true);
+	check_writable_kevent(s, 0, true);
+	close(s);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, getpeereid);
 	ATF_TP_ADD_TC(tp, send_0);
-	ATF_TP_ADD_TC(tp, full_not_writable);
+	ATF_TP_ADD_TC(tp, connected_writability);
+	ATF_TP_ADD_TC(tp, unconnected_writability);
+	ATF_TP_ADD_TC(tp, full_writability_select);
+	ATF_TP_ADD_TC(tp, full_writability_poll);
+	ATF_TP_ADD_TC(tp, full_writability_kevent);
 
 	return atf_no_error();
 }