mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-07 23:54:20 +07:00
Fix accept() error handling on Windows (#2561)
The accept loop in Server::listen_internal() classified accept() failures by reading errno, but Winsock reports them through WSAGetLastError() and never touches the CRT errno. Both retry branches were therefore dead code on Windows, and every accept() failure fell through to the fatal path, which closes the listening socket and ends listen(). That is reachable in normal operation: a peer resetting a pending connection before it is accepted is enough, and descriptor or buffer exhaustion shows up under load. One such event stopped the server from accepting anything again. Add is_accept_resource_error() and is_accept_transient_error() next to is_connection_error(), which already abstracts the same errno vs WSAGetLastError() difference, and use them in the accept loop. The POSIX sets are widened to match the Windows ones rather than being left as they were: ECONNABORTED is the POSIX spelling of the aborted pending connection that motivates this, and ENFILE, ENOBUFS and ENOMEM are resource exhaustion in the same sense as EMFILE.
This commit is contained in:
@@ -355,6 +355,65 @@ TEST(SetSocketOptTest, TcpNoDelay) {
|
||||
detail::close_socket(sock);
|
||||
}
|
||||
|
||||
TEST(AcceptErrorTest, RetryableFailuresAreClassifiedPerPlatform) {
|
||||
// accept() reports failures through WSAGetLastError() on Windows and through
|
||||
// errno everywhere else. Asking the wrong one is what made the accept loop
|
||||
// treat every recoverable failure as fatal on Windows.
|
||||
struct Classification {
|
||||
bool resource;
|
||||
bool transient;
|
||||
};
|
||||
|
||||
auto classify = [](int err) {
|
||||
#ifdef _WIN32
|
||||
WSASetLastError(err);
|
||||
#else
|
||||
errno = err;
|
||||
#endif
|
||||
// Read both before asserting: an assertion may clobber the error slot.
|
||||
Classification c;
|
||||
c.resource = detail::is_accept_resource_error();
|
||||
c.transient = detail::is_accept_transient_error();
|
||||
return c;
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
const std::vector<int> resource_errors = {WSAEMFILE, WSAENOBUFS};
|
||||
const std::vector<int> transient_errors = {WSAEINTR, WSAEWOULDBLOCK,
|
||||
WSAECONNRESET, WSAECONNABORTED};
|
||||
const std::vector<int> fatal_errors = {WSAENOTSOCK, WSAEINVAL, WSAEOPNOTSUPP};
|
||||
#else
|
||||
const std::vector<int> resource_errors = {EMFILE, ENFILE, ENOBUFS, ENOMEM};
|
||||
const std::vector<int> transient_errors = {EINTR, EAGAIN, EWOULDBLOCK,
|
||||
ECONNABORTED};
|
||||
const std::vector<int> fatal_errors = {EBADF, EINVAL, ENOTSOCK};
|
||||
#endif
|
||||
|
||||
for (auto err : resource_errors) {
|
||||
const auto c = classify(err);
|
||||
EXPECT_TRUE(c.resource) << "error " << err;
|
||||
EXPECT_FALSE(c.transient) << "error " << err;
|
||||
}
|
||||
|
||||
for (auto err : transient_errors) {
|
||||
const auto c = classify(err);
|
||||
EXPECT_TRUE(c.transient) << "error " << err;
|
||||
EXPECT_FALSE(c.resource) << "error " << err;
|
||||
}
|
||||
|
||||
for (auto err : fatal_errors) {
|
||||
const auto c = classify(err);
|
||||
EXPECT_FALSE(c.resource) << "error " << err;
|
||||
EXPECT_FALSE(c.transient) << "error " << err;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
WSASetLastError(0);
|
||||
#else
|
||||
errno = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST(ClientTest, MoveConstructible) {
|
||||
EXPECT_FALSE(std::is_copy_constructible<Client>::value);
|
||||
EXPECT_TRUE(std::is_nothrow_move_constructible<Client>::value);
|
||||
|
||||
Reference in New Issue
Block a user