Fix safety issues with AsyncClient destruction in user callbacks - #123
Fix safety issues with AsyncClient destruction in user callbacks#123willmmiles wants to merge 5 commits into
Conversation
Ensure that it is safe to destruct an AsyncClient from any user callback. Fixes a guaranteed use-after-free if `close()` was called inside `onData()`.
There was a problem hiding this comment.
Pull request overview
Adds callback-lifetime tracking to prevent AsyncClient use-after-free during user callbacks.
Changes:
- Tracks client validity across nested callbacks.
- Safely stops receive processing after closure/destruction.
- Initializes
_rx_ack_lenand removes a duplicate include.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/AsyncTCP.h |
Adds callback context state and moves ackLater() implementation. |
src/AsyncTCP.cpp |
Implements callback safety, receive cleanup, and initialization fixes. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // call to track if the object was destroyed. If so, we do not reset the pointer, which GCC identifies as a possible | ||
| // dangling reference case. It is not - the reference was destroyed along with the AsyncClient object. | ||
| #pragma GCC diagnostic push | ||
| #pragma GCC diagnostic ignored "-Wdangling-pointer" |
| return ERR_OK; | ||
| } | ||
|
|
||
| // In LwIP Thread |
|
Arg. Upon re-re-review, I think there might still be something off with the |
The old _lwip_fin function was entirely unreferenced.
Per @Copilot, it's only available in newer GCCs.
If we're closing as a response to some message from the remote end, acknowledge the last processed pbuf first. This ensures that we'll send FINs instead of RSTs on correctly processed messages.
Updated with a fix for this. To be clear, the issue of incorrectly sending RSTs if we |
Ensure no use-after-frees occur if the
AsyncClientobject is destroyed while in a user callback. This guarantees correct behavior if user code callsclose()in an event callback (such asonData,onPacket,onTimeout, etc.) and frees theAsyncClientin theonDisconnect()callback.Note that this does not insulate the user callback code itself against internal use-after-free events, such as using a captured lambda value after destroying the
AsyncClientand thus thestd::functionstoring the capture. Any user code that could destruct its ownAsyncClientmust be cognizant of the risks. We may want to consider an explicit "deferred close" API to make it easier for clients to reason about their own code.Includes a couple of other little tweaks:
_rx_ack_lenis initialized