NetBSD Problem Report #60786
From www@netbsd.org Thu Sep 24 22:00:56 2026
Return-Path: <www@netbsd.org>
Received: from mail.netbsd.org (mail.netbsd.org [199.233.217.200])
(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
client-signature RSA-PSS (2048 bits) client-digest SHA256)
(Client CN "mail.netbsd.org", Issuer "YR1" (not verified))
by mollari.NetBSD.org (Postfix) with ESMTPS id 766461A923B
for <gnats-bugs@gnats.NetBSD.org>; Thu, 24 Sep 2026 22:00:56 +0000 (UTC)
Message-Id: <20260924220055.4EC7B1A923C@mollari.NetBSD.org>
Date: Thu, 24 Sep 2026 22:00:55 +0000 (UTC)
From: campbell+netbsd@mumble.net
Reply-To: campbell+netbsd@mumble.net
To: gnats-bugs@NetBSD.org
Subject: poll reports socket writable after shutdown(SHUT_WR)
X-Send-Pr-Version: www-1.0
X-From4GNATS: "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetBSD.org>
>Number: 60786
>Category: kern
>Synopsis: poll reports socket writable after shutdown(SHUT_WR)
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Thu Sep 24 22:05:00 +0000 2026
>Last-Modified: Fri Sep 25 10:25:01 +0000 2026
>Originator: Taylor R Campbell
>Release: current, 11, 10, ...
>Organization:
The PollBSD Shutdown, Inc.
>Environment:
>Description:
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!
(This came up while trying to write a test that ensures SIGIO
is reported in a particular direction, by shutting down writes
so it won't report POLLOUT any more.)
>How-To-Repeat:
#include <sys/socket.h>
#include <err.h>
#include <poll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int
main(void)
{
int s[2];
struct pollfd pfd;
int nfds;
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, s) == -1)
err(EXIT_FAILURE, "socketpair");
if (shutdown(s[0], SHUT_WR) == -1)
err(EXIT_FAILURE, "shutdown");
pfd.fd = s[0];
pfd.events = POLLIN|POLLOUT;
nfds = poll(&pfd, 1, 0);
if (nfds == -1)
err(EXIT_FAILURE, "poll");
if (nfds == 1 && pfd.fd & (POLLIN|POLLOUT))
errx(EXIT_FAILURE, "bad");
return 0;
}
>Fix:
Yes, please!
>Audit-Trail:
From: Robert Elz <kre@munnari.OZ.AU>
To: gnats-bugs@netbsd.org
Cc:
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 05:49:25 +0700
Date: Thu, 24 Sep 2026 22:05:00 +0000 (UTC)
From: "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetBSD.org>
Message-ID: <20260924220500.D099A1A923E@mollari.NetBSD.org>
| 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).
If you want to know if something is writable, write() to it. If you
don't want the possibility of a hang while doing that, do a poll first,
if that indicates no hang will occur, do a write() (and if you really
want to be certain, turn on O_NONBLOCK as well). If the fd is closed,
shut down, revoked, or similar, the write will return an error.
kre
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Robert Elz <kre@munnari.OZ.AU>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
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>
>=20
> Date: Thu, 24 Sep 2026 22:05:00 +0000 (UTC)
> From: "campbell+netbsd@mumble.net via gnats" <gnats-admin@NetB=
SD.org>
> Message-ID: <20260924220500.D099A1A923E@mollari.NetBSD.org>
>=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--
From: Taylor R Campbell <riastradh@NetBSD.org>
To: Robert Elz <kre@munnari.OZ.AU>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 01:24:10 +0000
> Date: Fri, 25 Sep 2026 01:06:50 +0000
> From: Taylor R Campbell <riastradh@NetBSD.org>
>
> OK, in that case shutdown(SHUT_WR) should cause concurrent poll for
> POLLOUT to wake, but it doesn't!
Oops, disregard -- that was a bad test I drafted too quickly.
Will revisit this to see whether there is still an issue here, once
I'm done with the slew of other SIGIO/poll/fifo/socket/pipe issues a
little more confidently.
From: Robert Elz <kre@munnari.OZ.AU>
To: Taylor R Campbell <riastradh@NetBSD.org>
Cc: gnats-bugs@NetBSD.org, netbsd-bugs@NetBSD.org
Subject: Re: kern/60786: poll reports socket writable after shutdown(SHUT_WR)
Date: Fri, 25 Sep 2026 17:23:55 +0700
Date: Fri, 25 Sep 2026 01:24:10 +0000
From: Taylor R Campbell <riastradh@NetBSD.org>
Message-ID: <20260925012412.01FC684D78@mail.netbsd.org>
| Oops, disregard -- that was a bad test I drafted too quickly.
No problem, and I can certainly believe there are cases where
select/poll aren't woken when they should be, lots has been added
to the kernel since they were first created, and it would be easy
for bugs to have slipped in.
The only reason I replied initially, is because it is too easy to
fall into the rabbit hole of believing that if select/poll say
that an fd can be read/written, it should mean successfully.
It doesn't, and never did - they were just a mechanism (long before
BSD had O_NONBLOCK support in any form) to avoid hanging, and allow
a semblance of fake threading (before that existed either).
kre
(Contact us)
$NetBSD: query-full-pr,v 1.51 2026/08/10 02:28:17 riastradh Exp $
$NetBSD: gnats_config.sh,v 1.10 2026/05/13 22:00:09 riastradh Exp $
Copyright © 1994-2026
The NetBSD Foundation, Inc. ALL RIGHTS RESERVED.