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)



> Date: Fri, 25 Sep 2026 05:49:25 +0700
> From: Robert Elz <kre%munnari.OZ.AU@localhost>
> 
>     Date:        Thu, 24 Sep 2026 22:05:00 +0000 (UTC)
>     From:        "campbell+netbsd%mumble.net@localhost via gnats" <gnats-admin%NetBSD.org@localhost>
>     Message-ID:  <20260924220500.D099A1A923E%mollari.NetBSD.org@localhost>
> 
>   | 	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!
> 
> 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!
#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 = cookie;

	(void)pthread_barrier_wait(&C->bar);
	if (usleep(1) == -1)
		err(EXIT_FAILURE, "usleep");
	if (shutdown(C->peer, SHUT_WR) == -1)
		err(EXIT_FAILURE, "shutdown");
	(void)pthread_barrier_wait(&C->bar);
	if (usleep(1) == -1)
		err(EXIT_FAILURE, "usleep");
	if (close(C->fd) == -1)
		err(EXIT_FAILURE, "close");

	return NULL;
}

int
main(void)
{
	struct context ctx, *C = &ctx;
	int listener;
	union {
		struct sockaddr sa;
		struct sockaddr_in sin;
	} addr;
	socklen_t addrlen;
	int flags;
	char buf[BUFSIZ] = {0};
	pthread_t t;
	struct pollfd pfd;
	int nfds;

	memset(C, 0, sizeof(*C));
	errno = 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 = socket(AF_INET, SOCK_STREAM, 0);
	if (listener == -1)
		err(EXIT_FAILURE, "socket");

	memset(&addr, 0, sizeof(addr));
	addr.sin.sin_family = AF_INET;
	addr.sin.sin_port = htons(0);
	addr.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	if (bind(listener, &addr.sa, sizeof(addr.sin)) == -1)
		err(EXIT_FAILURE, "bind");
	if (listen(listener, 1) == -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 = sizeof(addr);
	if (getsockname(listener, &addr.sa, &addrlen) == -1)
		err(EXIT_FAILURE, "getsockname");

	/*
	 * Connect to the listener.
	 */
	C->fd = socket(AF_INET, SOCK_STREAM, 0);
	if (C->fd == -1)
		err(EXIT_FAILURE, "socket");
	if (connect(C->fd, &addr.sa, addrlen) == -1)
		err(EXIT_FAILURE, "connect");

	/*
	 * Accept the connetion.
	 */
	C->peer = accept(listener, NULL, NULL);
	if (C->peer == -1)
		err(EXIT_FAILURE, "accept");

	/*
	 * Make the peer nonblocking so we can fill its buffer to the
	 * brim.
	 */
	flags = fcntl(C->peer, F_GETFL);
	if (flags == -1)
		err(EXIT_FAILURE, "fcntl(F_GETFL)");
	if (fcntl(C->peer, F_SETFL, flags|O_NONBLOCK) == -1)
		err(EXIT_FAILURE, "fcntl(F_GETFL)");
	for (;;) {
		if (write(C->peer, buf, sizeof(buf)) == -1) {
			if (errno == EAGAIN)
				break;
			err(EXIT_FAILURE, "write");
		}
	}

	/*
	 * Verify that peer is NOT writable right now.
	 */
	pfd.fd = C->peer;
	pfd.events = POLLOUT|POLLWRNORM;
	nfds = poll(&pfd, 1, 0);
	if (nfds == -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 = C->peer;
	pfd.events = POLLOUT|POLLWRNORM;
	nfds = poll(&pfd, 1, 0);
	if (nfds == -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 = 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 = C->peer;
	pfd.events = POLLIN|POLLOUT|POLLRDNORM|POLLWRNORM;
	nfds = poll(&pfd, 1, -1);
	if (nfds == -1)
		err(EXIT_FAILURE, "poll");
	if (nfds != 1)
		errx(EXIT_FAILURE, "poll returned nfds%d", nfds);
	fprintf(stderr, "pfd.revents=0x%x\n", pfd.revents);
	if (pfd.events & (POLLOUT|POLLWRNORM))
		errx(EXIT_FAILURE, "writable but shutdown!");

	errno = pthread_join(t, NULL);
	if (errno)
		err(EXIT_FAILURE, "pthread_join");
	return 0;
}


Home | Main Index | Thread Index | Old Index