diff --git a/httplib.h b/httplib.h index d12a3f6..c52ba15 100644 --- a/httplib.h +++ b/httplib.h @@ -22843,6 +22843,14 @@ inline void WebSocket::start_heartbeat() { if (max_missed_pongs_ > 0 && unacked_pings_ >= max_missed_pongs_) { lock.unlock(); close(CloseStatus::GoingAway, "pong timeout"); + // Wake a read() blocked on the unresponsive peer. Only the read side + // is shut down: a TLS backend answers the EOF with an alert, and + // writing it to a socket closed for writing raises SIGPIPE. +#ifdef _WIN32 + shutdown(strm_.socket(), SD_RECEIVE); +#else + shutdown(strm_.socket(), SHUT_RD); +#endif return; } lock.unlock(); diff --git a/test/test_websocket_heartbeat.cc b/test/test_websocket_heartbeat.cc index 334ff10..4242a49 100644 --- a/test/test_websocket_heartbeat.cc +++ b/test/test_websocket_heartbeat.cc @@ -179,18 +179,20 @@ protected: TEST_F(WebSocketPongTimeoutTest, ClientDetectsNonResponsivePeer) { ws::WebSocketClient client("ws://localhost:" + std::to_string(port_) + "/ws"); client.set_websocket_max_missed_pongs(2); + // A read timeout asked for at runtime is reported as Timeout, so it cannot + // be mistaken for the Fail a pong timeout produces. + client.set_read_timeout(10); ASSERT_TRUE(client.connect()); ASSERT_TRUE(client.is_open()); // Client pings every 1s (compile-time default in this test file). // With max_missed_pongs = 2, the heartbeat thread should self-close within - // roughly 3s. Poll is_open() up to 6s. + // roughly 3s, and that has to end a read() already waiting on the peer. auto start = std::chrono::steady_clock::now(); - while (client.is_open() && - std::chrono::steady_clock::now() - start < std::chrono::seconds(6)) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } - + std::string msg; + EXPECT_EQ(client.read(msg), ws::Fail); + EXPECT_TRUE(std::chrono::steady_clock::now() - start < + std::chrono::seconds(6)); EXPECT_FALSE(client.is_open()); }