From a96c59780a62a7d2307f6bf415822bf378cfe466 Mon Sep 17 00:00:00 2001 From: yhirose Date: Mon, 28 Sep 2026 17:03:57 -0400 Subject: [PATCH] Enforce a minimum SSE reconnect wait to avoid a busy loop SSEClient::wait_for_reconnect() sleeps in 100ms steps until the reconnect interval has elapsed. With an interval of 0 (for example "retry: 0" from the server, or set_reconnect_interval(0)) it never slept at all, so a server that sends "retry: 0" and closes the stream made the client reconnect in a tight loop. set_max_reconnect_attempts() does not stop this either, because each successful connection resets the attempt counter. Always wait at least one step (100ms). Intervals of 1-99ms already waited 100ms because of the step size, so only 0 and negative values change behavior. --- README-sse.md | 2 +- httplib.h | 12 ++++++++---- test/test.cc | 3 +++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/README-sse.md b/README-sse.md index 223dcc32..47607ea7 100644 --- a/README-sse.md +++ b/README-sse.md @@ -69,7 +69,7 @@ sse.on_error([](httplib::Error err) { }); #### Configuration ```cpp -// Set reconnect interval (default: 3000ms) +// Set reconnect interval (default: 3000ms, minimum: 100ms) sse.set_reconnect_interval(5000); // Set max reconnect attempts (default: 0 = unlimited) diff --git a/httplib.h b/httplib.h index e64dae0c..7622b740 100644 --- a/httplib.h +++ b/httplib.h @@ -5051,11 +5051,15 @@ inline bool SSEClient::should_reconnect(int count) const { } inline void SSEClient::wait_for_reconnect() { - // Use small increments to check running_ flag frequently + // Use small increments to check running_ flag frequently. + // Always wait at least one increment, so that a zero interval (e.g. + // "retry: 0" from the server) cannot cause a busy reconnect loop. + const auto step_ms = 100; + auto interval_ms = (std::max)(reconnect_interval_ms_, step_ms); auto waited = 0; - while (running_.load() && waited < reconnect_interval_ms_) { - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - waited += 100; + while (running_.load() && waited < interval_ms) { + std::this_thread::sleep_for(std::chrono::milliseconds(step_ms)); + waited += step_ms; } } diff --git a/test/test.cc b/test/test.cc index 62dddece..0cdeaa1b 100644 --- a/test/test.cc +++ b/test/test.cc @@ -22702,7 +22702,10 @@ TEST_F(SSEIntegrationTest, DigitRetryFieldApplied) { std::this_thread::sleep_for(std::chrono::milliseconds(500)); sse.stop(); + // The server-supplied interval is applied, but never below 100ms, so + // "retry: 0" does not cause a busy reconnect loop EXPECT_GE(connection_count.load(), 2); + EXPECT_LE(connection_count.load(), 10); } // Test: Last-Event-ID sent on reconnect