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