You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Adds a stable, source-scoped ID for each Server-Sent Events connection and an ownership-safe way to close one connection by ID.
The change also adds onConnectWithRequest(...), so an application can associate the connection ID with request-scoped state such as an authenticated session without retaining an AsyncEventSourceClient* after the callback.
Why
Long-lived SSE is now commonly used by authenticated dashboards and device management interfaces. When one session expires or logs out, applications need to close that session's stream while leaving unrelated users connected.
Today the library exposes an individual AsyncEventSourceClient::close(), but the application receives only a temporary raw pointer and cannot safely look the client up later. The only owner-level operation is AsyncEventSource::close(), which closes every SSE client. Retaining callback pointers risks use-after-free; closing the entire source disrupts other active sessions and creates avoidable reconnect demand.
API
AsyncEventSourceClient::id() returns a nonzero ID stable for that connection.
AsyncEventSource::onConnectWithRequest(...) exposes request metadata during connection setup while preserving the existing onConnect(...) API.
AsyncEventSource::closeClient(id) transfers ownership out of the shared client list before entering AsyncTCP and restores ownership if graceful close is deferred.
IDs are scoped to one AsyncEventSource. Applications retain the ID, not the client pointer, and call closeClient() later from their normal service context.
Compatibility
The existing connect callback, disconnect callback, broadcast send, close(), and client constructor calls remain source compatible. The new request-aware callback has a distinct name, avoiding overload ambiguity for existing onConnect(nullptr) callers.
Validation status
This PR is a draft. Validation completed so far is deliberately narrow:
Compiled as part of a full ESP32 firmware using AsyncTCP 3.5.0 and the older Arduino ESP32 2.0.17 core.
Live two-session test on that firmware: both SSE streams connected; invalidating session A closed only A; session B stayed connected and authenticated.
The ServerSentEvents example demonstrates the request-aware callback and stable client ID, but the upstream example matrix has not yet passed locally.
Before this is marked ready for review, it still needs the repository CI matrix across current supported ESP32, ESP8266, RP2040 and LibreTiny targets, plus focused lifecycle coverage for immediate disconnect, deferred close and ID lookup after disconnect.
The application-level live test also confirmed this is a targeted lifecycle improvement. It does not change lwIP TCP PCB capacity or claim to solve overload from many simultaneously active HTTP connections.
First off, thank you so much for putting the request in context. It's much more helpful for evaluating design alternatives.
I think there's a broader scope issue here - it's not just close(), but realistically any operation on an AsyncEventSourceClient object isn't safe. I can also envision cases where an application wants to send a message to some subset of clients.
I'm thinking the cleaner answer here is for AsyncEventSource to hold shared_ptr<AsyncEventSourceClient>s instead of raw pointers, and leverage enable_shared_from_this on AsyncEventSourceClient so the onConnect() callback can take a stable shared_ptr or weak_ptr to it. This would allow applications to track client metadata themselves in a safe way, though it may be necessary to mutex the data structures built in one's onConnect and onDisconnect callbacks if they're being interacted with in other tasks. It would also allow making a safe copy of the client list for inspection; the client objects themselves may transition to a "disconnected" state asynchronously, but they wouldn't destruct unexpectedly. Finally, those objects would be usable as stable identifiers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a stable, source-scoped ID for each Server-Sent Events connection and an ownership-safe way to close one connection by ID.
The change also adds
onConnectWithRequest(...), so an application can associate the connection ID with request-scoped state such as an authenticated session without retaining anAsyncEventSourceClient*after the callback.Why
Long-lived SSE is now commonly used by authenticated dashboards and device management interfaces. When one session expires or logs out, applications need to close that session's stream while leaving unrelated users connected.
Today the library exposes an individual
AsyncEventSourceClient::close(), but the application receives only a temporary raw pointer and cannot safely look the client up later. The only owner-level operation isAsyncEventSource::close(), which closes every SSE client. Retaining callback pointers risks use-after-free; closing the entire source disrupts other active sessions and creates avoidable reconnect demand.API
AsyncEventSourceClient::id()returns a nonzero ID stable for that connection.AsyncEventSource::onConnectWithRequest(...)exposes request metadata during connection setup while preserving the existingonConnect(...)API.AsyncEventSource::closeClient(id)transfers ownership out of the shared client list before entering AsyncTCP and restores ownership if graceful close is deferred.IDs are scoped to one
AsyncEventSource. Applications retain the ID, not the client pointer, and callcloseClient()later from their normal service context.Compatibility
The existing connect callback, disconnect callback, broadcast send,
close(), and client constructor calls remain source compatible. The new request-aware callback has a distinct name, avoiding overload ambiguity for existingonConnect(nullptr)callers.Validation status
This PR is a draft. Validation completed so far is deliberately narrow:
Before this is marked ready for review, it still needs the repository CI matrix across current supported ESP32, ESP8266, RP2040 and LibreTiny targets, plus focused lifecycle coverage for immediate disconnect, deferred close and ID lookup after disconnect.
The application-level live test also confirmed this is a targeted lifecycle improvement. It does not change lwIP TCP PCB capacity or claim to solve overload from many simultaneously active HTTP connections.