Repository navigation
Conversation
|
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 I'm thinking the cleaner answer here is for |
|
This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 7 days. |
|
This PR was closed because it has been stalled for 7 days with no activity. |
|
@willmmiles @horendus : the bot closed this PR. I will leave it closed because the proposed code won't be merged anyway, but feel free to open an issue wrt what Will said. Will's proposal would require if I understand to pass the shared ptr to the callback, which is a breaking API change. |
|
@willmmiles @horendus : please look at #512 |
AsyncEventSource clients were stored as unique_ptr and exposed to applications only as raw pointers inside callbacks, making it impossible to safely target one connection later (close one session, send to a subset) without use-after-free risk. Rework client lifetime after the design suggested in review of #483: - AsyncEventSourceClient now derives from enable_shared_from_this and the server stores shared_ptr handles; all AsyncTCP callbacks pin the client for the duration of the call so no callback can free the object while its frame is still live. - Client construction/registration moves from the constructor (where shared_from_this() is illegal) to AsyncEventSourceResponse::_respond() via _addClient(); the request is pinned for the connect callbacks. - New app API: onConnectSafe()/onDisconnectSafe() deliver a stable AsyncEventSourceClientPtr handle that may be stored past the callback and used as a connection identifier; onConnectWithRequest() adds the originating request for session association; getClients() returns a safe snapshot. The classic onConnect()/onDisconnect() signatures are unchanged (wrapped into the shared slot, last registered wins). - close() iterates a snapshot instead of the live list (fixes iterator invalidation on synchronous disconnect), ~AsyncEventSource detaches surviving clients, and an app-dropped last handle now closes the underlying connection safely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
AsyncEventSource clients were stored as unique_ptr and exposed to applications only as raw pointers inside callbacks, making it impossible to safely target one connection later (close one session, send to a subset) without use-after-free risk. Rework client lifetime after the design suggested in review of #483: - AsyncEventSourceClient now derives from enable_shared_from_this and the server stores shared_ptr handles; all AsyncTCP callbacks pin the client for the duration of the call so no callback can free the object while its frame is still live. - Client construction/registration moves from the constructor (where shared_from_this() is illegal) to AsyncEventSourceResponse::_respond() via _addClient(); the request is pinned for the connect callbacks. - New app API: onConnectSafe()/onDisconnectSafe() deliver a stable AsyncEventSourceClientPtr handle that may be stored past the callback and used as a connection identifier; onConnectWithRequest() adds the originating request for session association; getClients() returns a safe snapshot. The classic onConnect()/onDisconnect() signatures are unchanged (wrapped into the shared slot, last registered wins). - close() iterates a snapshot instead of the live list (fixes iterator invalidation on synchronous disconnect), ~AsyncEventSource detaches surviving clients, and an app-dropped last handle now closes the underlying connection safely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.