Guard last_event_id() against the async event loop in SSEClient (#2644)

last_event_id() returned a reference to a string that run_event_loop() assigns on its own thread under start_async(), so a caller reading it from another thread raced the writes. The member is now guarded by a mutex and the getter returns a copy.

The return type changes from const std::string & to std::string. Code that binds the result to a const reference or copies it still compiles.
This commit is contained in:
metsw24-max authored and GitHub committed 2026-10-25 22:21:55 -04:00
1 parent 391de32115
commit 2f8b9f6f57
3 files changed
+51 -4

No files matched your search

+9 -3
View File
@@ -4498,7 +4498,7 @@ public:
// State accessors
bool is_connected() const;
const std::string &last_event_id() const;
std::string last_event_id() const;
// Blocking start - runs event loop with auto-reconnect
void start();
@@ -4536,7 +4536,9 @@ private:
// State
std::atomic<bool> running_{false};
std::atomic<bool> connected_{false};
// Written by the event loop, which start_async() runs on its own thread
std::string last_event_id_;
mutable std::mutex last_event_id_mutex_;
// Async support
std::thread async_thread_;
@@ -5034,7 +5036,8 @@ inline SSEClient &SSEClient::set_headers(const Headers &headers) {
inline bool SSEClient::is_connected() const { return connected_.load(); }
inline const std::string &SSEClient::last_event_id() const {
inline std::string SSEClient::last_event_id() const {
std::lock_guard<std::mutex> lock(last_event_id_mutex_);
return last_event_id_;
}
@@ -5209,7 +5212,10 @@ inline void SSEClient::run_event_loop() {
if (event_complete) {
// Update last_event_id for reconnection, even for an event that
// has no data. An empty id clears it.
if (has_id) { last_event_id_ = current_msg.id; }
if (has_id) {
std::lock_guard<std::mutex> lock(last_event_id_mutex_);
last_event_id_ = current_msg.id;
}
// An event without a data field is not dispatched
if (has_data) { dispatch_event(current_msg); }