NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)



The following reply was made to PR kern/60786; it has been noted by GNATS.

From: Taylor R Campbell <riastradh%NetBSD.org@localhost>
To: Robert Elz <kre%munnari.OZ.AU@localhost>
Cc: gnats-bugs%NetBSD.org@localhost, netbsd-bugs%NetBSD.org@localhost
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 01:06:50 +0000

 This is a multi-part message in MIME format.
 --=_nsoQjY3CD7bBvFEdm/FiyAa+axP8majm
 Content-Transfer-Encoding: quoted-printable
 
 > Date: Fri, 25 Sep 2026 05:49:25 +0700
 > From: Robert Elz <kre%munnari.OZ.AU@localhost>
 >=20
 >     Date:        Thu, 24 Sep 2026 22:05:00 +0000 (UTC)
 >     From:        "campbell+netbsd%mumble.net@localhost via gnats" <gnats-admin@NetB=
 SD.org>
 >     Message-ID:  <20260924220500.D099A1A923E%mollari.NetBSD.org@localhost>
 >=20
 >   | 	I don't know if there's language in POSIX about this but surely
 >   | 	it is nonsensical for poll to claim that a socket is writable
 >   | 	after it has been shut down in the writing direction!
 >=20
 > poll() (and select()) have never claimed anything was writable or
 > readable - they merely indicate whether or not a write (or read) will
 > hang if attempted at the time of the poll/select (and assuming some
 > other process/thread isn't manipulating the same object).
 
 OK, in that case shutdown(SHUT_WR) should cause concurrent poll for
 POLLOUT to wake, but it doesn't!
 
 --=_nsoQjY3CD7bBvFEdm/FiyAa+axP8majm
 Content-Type: text/plain; charset="ISO-8859-1"; name="shutpoll"
 Content-Transfer-Encoding: quoted-printable
 Content-Disposition: attachment; filename="shutpoll.c"
 
 #include <sys/socket.h>
 
 #include <netinet/in.h>
 
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <poll.h>
 #include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
 
 struct context {
 	pthread_barrier_t bar;
 	int peer;
 	int fd;
 };
 
 static void *
 start_thread(void *cookie)
 {
 	struct context *C =3D cookie;
 
 	(void)pthread_barrier_wait(&C->bar);
 	if (usleep(1) =3D=3D -1)
 		err(EXIT_FAILURE, "usleep");
 	if (shutdown(C->peer, SHUT_WR) =3D=3D -1)
 		err(EXIT_FAILURE, "shutdown");
 	(void)pthread_barrier_wait(&C->bar);
 	if (usleep(1) =3D=3D -1)
 		err(EXIT_FAILURE, "usleep");
 	if (close(C->fd) =3D=3D -1)
 		err(EXIT_FAILURE, "close");
 
 	return NULL;
 }
 
 int
 main(void)
 {
 	struct context ctx, *C =3D &ctx;
 	int listener;
 	union {
 		struct sockaddr sa;
 		struct sockaddr_in sin;
 	} addr;
 	socklen_t addrlen;
 	int flags;
 	char buf[BUFSIZ] =3D {0};
 	pthread_t t;
 	struct pollfd pfd;
 	int nfds;
 
 	memset(C, 0, sizeof(*C));
 	errno =3D pthread_barrier_init(&C->bar, NULL, 2);
 	if (errno)
 		err(EXIT_FAILURE, "pthread_barrier_init");
 
 	/*
 	 * Listen on a random port number on 127.0.0.1.
 	 */
 	listener =3D socket(AF_INET, SOCK_STREAM, 0);
 	if (listener =3D=3D -1)
 		err(EXIT_FAILURE, "socket");
 
 	memset(&addr, 0, sizeof(addr));
 	addr.sin.sin_family =3D AF_INET;
 	addr.sin.sin_port =3D htons(0);
 	addr.sin.sin_addr.s_addr =3D htonl(INADDR_LOOPBACK);
 	if (bind(listener, &addr.sa, sizeof(addr.sin)) =3D=3D -1)
 		err(EXIT_FAILURE, "bind");
 	if (listen(listener, 1) =3D=3D -1)
 		err(EXIT_FAILURE, "listen");
 
 	/*
 	 * Get the socket address that was assigned so we can connect
 	 * to it (we need to find the port number).
 	 */
 	addrlen =3D sizeof(addr);
 	if (getsockname(listener, &addr.sa, &addrlen) =3D=3D -1)
 		err(EXIT_FAILURE, "getsockname");
 
 	/*
 	 * Connect to the listener.
 	 */
 	C->fd =3D socket(AF_INET, SOCK_STREAM, 0);
 	if (C->fd =3D=3D -1)
 		err(EXIT_FAILURE, "socket");
 	if (connect(C->fd, &addr.sa, addrlen) =3D=3D -1)
 		err(EXIT_FAILURE, "connect");
 
 	/*
 	 * Accept the connetion.
 	 */
 	C->peer =3D accept(listener, NULL, NULL);
 	if (C->peer =3D=3D -1)
 		err(EXIT_FAILURE, "accept");
 
 	/*
 	 * Make the peer nonblocking so we can fill its buffer to the
 	 * brim.
 	 */
 	flags =3D fcntl(C->peer, F_GETFL);
 	if (flags =3D=3D -1)
 		err(EXIT_FAILURE, "fcntl(F_GETFL)");
 	if (fcntl(C->peer, F_SETFL, flags|O_NONBLOCK) =3D=3D -1)
 		err(EXIT_FAILURE, "fcntl(F_GETFL)");
 	for (;;) {
 		if (write(C->peer, buf, sizeof(buf)) =3D=3D -1) {
 			if (errno =3D=3D EAGAIN)
 				break;
 			err(EXIT_FAILURE, "write");
 		}
 	}
 
 	/*
 	 * Verify that peer is NOT writable right now.
 	 */
 	pfd.fd =3D C->peer;
 	pfd.events =3D POLLOUT|POLLWRNORM;
 	nfds =3D poll(&pfd, 1, 0);
 	if (nfds =3D=3D -1)
 		err(EXIT_FAILURE, "nfds");
 	if (pfd.revents & (POLLOUT|POLLWRNORM))
 		errx(EXIT_FAILURE, "writable but the buffer is full! [1]");
 
 	/*
 	 * Wait for the thread to shutdown writes.  After this point,
 	 * we cannot write any further, so there should be no reason
 	 * for poll to report a transition to writable.
 	 */
 	alarm(1);
 	(void)pthread_barrier_wait(&C->bar);
 	pfd.fd =3D C->peer;
 	pfd.events =3D POLLOUT|POLLWRNORM;
 	nfds =3D poll(&pfd, 1, 0);
 	if (nfds =3D=3D -1)
 		err(EXIT_FAILURE, "nfds");
 	if (pfd.revents & (POLLOUT|POLLWRNORM))
 		errx(EXIT_FAILURE, "writable but the buffer is full! [2]");
 
 	/*
 	 * Start a thread to close the socket.
 	 */
 	errno =3D pthread_create(&t, NULL, &start_thread, C);
 	if (errno)
 		err(EXIT_FAILURE, "pthread_create");
 
 	/*
 	 * Wait to start and see what poll says is ready.
 	 */
 	(void)pthread_barrier_wait(&C->bar);
 
 	/*
 	 * See what I/O is available.
 	 */
 	pfd.fd =3D C->peer;
 	pfd.events =3D POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM;
 	nfds =3D poll(&pfd, 1, -1);
 	if (nfds =3D=3D -1)
 		err(EXIT_FAILURE, "poll");
 	if (nfds !=3D 1)
 		errx(EXIT_FAILURE, "poll returned nfds%d", nfds);
 	fprintf(stderr, "pfd.revents=3D0x%x\n", pfd.revents);
 	if (pfd.events & (POLLOUT|POLLWRNORM))
 		errx(EXIT_FAILURE, "writable but shutdown!");
 
 	errno =3D pthread_join(t, NULL);
 	if (errno)
 		err(EXIT_FAILURE, "pthread_join");
 	return 0;
 }
 
 --=_nsoQjY3CD7bBvFEdm/FiyAa+axP8majm--
 



Home | Main Index | Thread Index | Old Index