Deliver the whole answer to a half-closed client

When the client shuts down its sending side, sockmap() shuts down the
server's in turn, and once the server has answered and closed, poll()
reports a hang-up on its socket. A socket sockmap() had nothing to ask
of at that moment, its buffer being full for a slow client, was still
polled, and a hang-up there ended both directions: whatever of the
answer was still unread in the kernel was dropped and the client got an
end of stream in the middle of it, megabytes short with large answers.

A hang-up on such a socket now only marks it: what is left is read
once the buffer has room, up to the end of the stream as usual. Errors
still end the connection. Since poll() reports a hang-up whatever is
asked, the socket is left out of poll() while nothing is asked of it.
The same holds for the client's side, for an upload cut short after the
server half-closes.

(cherry picked from commit 8c87b43, without its test: 0.9 has no test
suite)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Vladimir Dubrovin 2026-10-13 12:22:24 +03:00
parent 29df50528a
commit d211821240

View File

@ -60,6 +60,8 @@ int sockmap(struct clientparam * param, int timeo, int usesplice){
#endif
int HASERROR=0;
int CLIENTTERMREAD = 0, CLIENTTERMWRITE = 0, SERVERTERMREAD = 0, SERVERTERMWRITE = 0;
/* the side has hung up: closed by the peer after its own was shut down */
int CLIENTHUP = 0, SERVERHUP = 0;
int after = 0;
struct pollfd fds[8];
int fdsc = 0;
@ -569,6 +571,9 @@ log("done read from server to buf");
#endif
))
cli_events |= POLLOUT;
/* poll() reports a hang-up whatever is asked, and would
return at once every time: wait for the other side then */
if(!cli_events && CLIENTHUP) fds[fdsc].fd = INVALID_SOCKET;
if(cli_events){
fds[fdsc].fd = param->clisock;
fds[fdsc].events = cli_events;
@ -604,9 +609,13 @@ log("ready to write to client");
if(fds[fdsc].events & POLLOUT) CLIENTTERMWRITE = 1;
}
}
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL|POLLHUP)) {
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
CLIENTTERMREAD = 1;
CLIENTTERMWRITE = 1;
} else if(fds[fdsc].revents & POLLHUP) {
/* what the client sent before it closed is still to be
read, once the buffer has room for it */
CLIENTHUP = 1;
}
fdsc++;
@ -629,6 +638,7 @@ log("ready to write to client");
#endif
))
srv_events |= POLLOUT;
if(!srv_events && SERVERHUP) fds[fdsc].fd = INVALID_SOCKET;
if(srv_events){
fds[fdsc].fd = param->remsock;
fds[fdsc].events = srv_events;
@ -671,9 +681,12 @@ log("server terminated connection");
if(fds[fdsc].events & POLLOUT) SERVERTERMWRITE = 1;
}
}
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL|POLLHUP)) {
} else if(fds[fdsc].revents & (POLLERR|POLLNVAL)) {
SERVERTERMREAD = 1;
SERVERTERMWRITE = 1;
} else if(fds[fdsc].revents & POLLHUP) {
/* the rest of the answer may still be waiting to be read */
SERVERHUP = 1;
}
fdsc++;
// }