mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-19 11:19:36 +00:00
Let SSEClient::stop() interrupt a blocked read
The event loop reads through open_stream(), which takes the socket out of the Client, so the client_.stop() in stop() could not reach it. stop(), and the destructor with it, waited until the server sent something or the read timeout passed, 300 seconds by default. The loop now hands the stream's socket to stop() while it reads, and withdraws it under the same lock before the socket is closed. stop() shuts down the read side only: a TLS backend answers the EOF with an alert, and writing it to a socket closed for writing raises SIGPIPE.
This commit is contained in:
1 parent
30879bb48b
commit
3744227de2
2 files changed
+104
No files matched your search
@@ -25469,6 +25469,78 @@ TEST_F(SSEIntegrationTest, ManualStop) {
|
||||
EXPECT_FALSE(sse.is_connected());
|
||||
}
|
||||
|
||||
// Sends one event, then nothing until stop is set
|
||||
static Server::Handler idle_event_stream(std::atomic<bool> &stop) {
|
||||
return [&stop](const Request &, Response &res) {
|
||||
res.set_chunked_content_provider(
|
||||
"text/event-stream", [&stop](size_t offset, DataSink &sink) {
|
||||
if (offset == 0) {
|
||||
std::string event = "data: hello\n\n";
|
||||
sink.write(event.data(), event.size());
|
||||
}
|
||||
while (!stop.load()) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Both stop() and the destructor end a read blocked on /idle-stream promptly
|
||||
static void check_stop_interrupts_blocked_read(Client &client) {
|
||||
client.set_read_timeout(30, 0);
|
||||
|
||||
for (auto by_destructor : {false, true}) {
|
||||
std::atomic<bool> received{false};
|
||||
std::chrono::steady_clock::time_point start;
|
||||
{
|
||||
sse::SSEClient sse(client, "/idle-stream");
|
||||
sse.on_message([&](const sse::SSEMessage &) { received.store(true); });
|
||||
sse.start_async();
|
||||
|
||||
for (int i = 0; i < 500 && !received.load(); ++i) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||
}
|
||||
ASSERT_TRUE(received.load());
|
||||
// Let the client block in the next read
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
|
||||
start = std::chrono::steady_clock::now();
|
||||
if (!by_destructor) { sse.stop(); }
|
||||
}
|
||||
EXPECT_LT(std::chrono::steady_clock::now() - start,
|
||||
std::chrono::seconds(2));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SSEIntegrationTest, StopInterruptsBlockedRead) {
|
||||
server_->Get("/idle-stream", idle_event_stream(stop_server_));
|
||||
|
||||
Client client("localhost", get_port());
|
||||
check_stop_interrupts_blocked_read(client);
|
||||
}
|
||||
|
||||
#ifdef CPPHTTPLIB_SSL_ENABLED
|
||||
TEST(SSEClientSSLTest, StopInterruptsBlockedRead) {
|
||||
SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
|
||||
std::atomic<bool> done{false};
|
||||
svr.Get("/idle-stream", idle_event_stream(done));
|
||||
|
||||
auto port = svr.bind_to_any_port(HOST);
|
||||
std::thread t([&] { svr.listen_after_bind(); });
|
||||
auto se = detail::scope_exit([&] {
|
||||
done.store(true);
|
||||
svr.stop();
|
||||
t.join();
|
||||
});
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client client("https://localhost:" + std::to_string(port));
|
||||
client.enable_server_certificate_verification(false);
|
||||
check_stop_interrupts_blocked_read(client);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Test: SSEClient with custom headers
|
||||
TEST_F(SSEIntegrationTest, CustomHeaders) {
|
||||
// Setup a server endpoint that checks for custom header
|
||||
|
||||
Reference in new issue
Block a user