Write to a peer that hung up while it still reads, on Windows

sockmap() took a hang-up on a socket it had data to write to for a peer
it could no longer write to. With poll() a hang-up comes once both sides
are shut down, so that held. WSAPoll reports one as soon as the peer has
half-closed, still reading, and a half-closed client got nothing on
Windows: the test added with the previous fix failed there.

The write is now tried, and a peer really gone makes it fail, which
ends the connection as before. WSAPoll keeps reporting the hang-up
whether the socket has room or not, so on Windows room is waited for
with select() first unless poll() reported it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ddefa40affb76de267a8be1cf59a183a8eb77e32)
This commit is contained in:
Vladimir Dubrovin 2026-10-13 12:52:47 +03:00
parent 993c1ef95e
commit 84dd84a993

View File

@ -61,6 +61,28 @@ ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t le
before the sockets are closed. */ before the sockets are closed. */
#define SESSIONEND (!halfclose && (CLIENTTERMREAD || SERVERTERMREAD)) #define SESSIONEND (!halfclose && (CLIENTTERMREAD || SERVERTERMREAD))
/* A hang-up on a socket there is data to write to. With poll() it comes
once both sides are shut down, and the write tried then fails, which ends
it. WSAPoll reports it as soon as the peer has half-closed, a peer still
reading, and keeps reporting it whether the socket has room or not: room
is waited for with select() then, up to timeo seconds. Returns whether to
try the write. */
static int hupwrite(SOCKET s, short revents, int timeo){
#ifdef _WIN32
if(!(revents & POLLOUT)){
fd_set wfd;
struct timeval tv;
FD_ZERO(&wfd);
FD_SET(s, &wfd);
tv.tv_sec = timeo;
tv.tv_usec = 0;
if(select(0, NULL, &wfd, NULL, &tv) < 1) return 0;
}
#endif
return 1;
}
/* usesplice is a mask of the directions passed with splice() rather than /* usesplice is a mask of the directions passed with splice() rather than
through a buffer: SPLICE_DOWNLOAD, server to client, and SPLICE_UPLOAD, through a buffer: SPLICE_DOWNLOAD, server to client, and SPLICE_UPLOAD,
client to server. */ client to server. */
@ -677,7 +699,10 @@ log("ready to write to client");
} }
if(fds[fdsc].revents & (POLLHUP)) { if(fds[fdsc].revents & (POLLHUP)) {
if(fds[fdsc].events & POLLIN) FROMCLIENT = 1; if(fds[fdsc].events & POLLIN) FROMCLIENT = 1;
if(fds[fdsc].events & POLLOUT) CLIENTTERMWRITE = 1; if(fds[fdsc].events & POLLOUT){
if(hupwrite(param->clisock, fds[fdsc].revents, timeo)) TOCLIENT = 1;
else CLIENTTERMWRITE = 1;
}
} }
} }
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) { } else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
@ -742,7 +767,10 @@ log("ready to write to server");
log("server terminated connection"); log("server terminated connection");
#endif #endif
if(fds[fdsc].events & POLLIN) FROMSERVER = 1; if(fds[fdsc].events & POLLIN) FROMSERVER = 1;
if(fds[fdsc].events & POLLOUT) SERVERTERMWRITE = 1; if(fds[fdsc].events & POLLOUT){
if(hupwrite(param->remsock, fds[fdsc].revents, timeo)) TOSERVER = 1;
else SERVERTERMWRITE = 1;
}
} }
} }
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) { } else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {