mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-09-30 12:42:26 +07:00
Gracefully drain socket before close in Server::process_and_close_socket (#2534)
* Gracefully drain socket before close in Server::process_and_close_socket Closing a connection while the receive queue still has unread data, or while bytes are still in flight, can make the OS send an abortive RST instead of a graceful FIN. On Windows this surfaces as WSAECONNABORTED/WSAECONNRESET on the peer's read, which can make an otherwise fully-written response look like a failed request -- a likely contributor to the ServerTest.HTTP2Magic flakiness tracked in #2533. Add detail::close_socket_gracefully(), which half-closes the write side, drains any queued/in-flight bytes (bounded to 100ms / 1MB), then performs the final shutdown+close. Use it in Server::process_and_close_socket. Root cause and fix mechanism identified by @Hyukya in #2533. * Rename close_socket_gracefully to drain_and_close_socket 'gracefully' already means something specific in this codebase: whether to send a TLS close_notify before closing (shutdown_ssl's shutdown_gracefully param, ClientImpl::disconnect(gracefully), tls::shutdown(session, graceful)). Reusing the word for an unrelated TCP-level drain-before-close made the new function read as part of that TLS machinery when it isn't. Rename it to describe what it does instead, matching the existing close_socket/shutdown_socket and WebSocketClient::shutdown_and_close naming.
This commit is contained in:
@@ -6321,6 +6321,39 @@ inline int shutdown_socket(socket_t sock) noexcept {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Half-closes the write side and drains any in-flight/queued bytes before
|
||||||
|
// the final shutdown+close. Closing with unread data in the receive queue
|
||||||
|
// (or bytes arriving after the receive side is closed) makes the stack send
|
||||||
|
// an abortive RST instead of a graceful FIN, which can make the peer see the
|
||||||
|
// response as a failed read even though it was fully written.
|
||||||
|
inline void drain_and_close_socket(socket_t sock) noexcept {
|
||||||
|
#ifdef _WIN32
|
||||||
|
shutdown(sock, SD_SEND);
|
||||||
|
#else
|
||||||
|
shutdown(sock, SHUT_WR);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
char buf[CPPHTTPLIB_RECV_BUFSIZ];
|
||||||
|
size_t total = 0;
|
||||||
|
const auto deadline = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::milliseconds(100); // bound #1
|
||||||
|
|
||||||
|
while (total < size_t(1024u * 1024u)) { // bound #2
|
||||||
|
const auto remaining =
|
||||||
|
std::chrono::duration_cast<std::chrono::microseconds>(
|
||||||
|
deadline - std::chrono::steady_clock::now())
|
||||||
|
.count();
|
||||||
|
if (remaining <= 0) { break; }
|
||||||
|
if (select_read(sock, 0, static_cast<time_t>(remaining)) <= 0) { break; }
|
||||||
|
const auto n = read_socket(sock, buf, sizeof(buf), CPPHTTPLIB_RECV_FLAGS);
|
||||||
|
if (n <= 0) { break; }
|
||||||
|
total += static_cast<size_t>(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown_socket(sock);
|
||||||
|
close_socket(sock);
|
||||||
|
}
|
||||||
|
|
||||||
inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
|
inline std::string escape_abstract_namespace_unix_domain(const std::string &s) {
|
||||||
if (s.size() > 1 && s[0] == '\0') {
|
if (s.size() > 1 && s[0] == '\0') {
|
||||||
auto ret = s;
|
auto ret = s;
|
||||||
@@ -13608,8 +13641,7 @@ inline bool Server::process_and_close_socket(socket_t sock) {
|
|||||||
nullptr, &websocket_upgraded);
|
nullptr, &websocket_upgraded);
|
||||||
});
|
});
|
||||||
|
|
||||||
detail::shutdown_socket(sock);
|
detail::drain_and_close_socket(sock);
|
||||||
detail::close_socket(sock);
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user