fix(http_client): don't hold mutex_ across the OnTcpData backpressure wait - #53
Open
max-power wants to merge 1 commit into
Open
fix(http_client): don't hold mutex_ across the OnTcpData backpressure wait#53max-power wants to merge 1 commit into
max-power wants to merge 1 commit into
Conversation
… wait OnTcpData() acquired mutex_ for the whole function, including while waiting on write_cv_ for body_chunks_ to drain (or for connected_ to become false). But connected_ can only be set by OnTcpDisconnected(), which itself needs to acquire mutex_ first — so if the receive task is blocked in that wait when the connection drops, OnTcpDisconnected() can never acquire the lock to update connected_, and the two calls deadlock permanently (the receive task spins forever inside the critical section a mutex unlock/notify enters, which can starve the other core's own interrupt handling and eventually trip the interrupt watchdog). Neither the wait predicate nor OnTcpDisconnected() ever touches anything mutex_ protects — the predicate only reads read_mutex_- protected body_chunks_ and connected_, and OnTcpDisconnected() never touches body_chunks_/read_mutex_ at all — so narrowing mutex_'s scope to just the rx_buffer_/ProcessReceivedData() call after the wait doesn't change any protected invariant, it just stops holding an unrelated lock across a blocking wait. Most likely to surface with a large/slow upload where the server aborts the connection mid-transfer (a fast small request completes before body_chunks_ ever needs to throttle, so the wait — and the deadlock — never triggers). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
HttpClient::OnTcpData()acquiresmutex_for the entire function, including while it waits onwrite_cv_forbody_chunks_to drain (or forconnected_to become false).connected_can only be set tofalsebyHttpClient::OnTcpDisconnected()— which itself needs to acquire that samemutex_first.OnTcpDisconnected()(called fromEspTcp's disconnect path, typically from the receive task itself or a related callback) can never acquiremutex_to setconnected_ = false, which is exactly the condition the wait needs to unblock. The two deadlock permanently.Neither the
write_cv_wait predicate norOnTcpDisconnected()ever touches anythingmutex_protects — the predicate only readsread_mutex_-protectedbody_chunks_andconnected_, andOnTcpDisconnected()never touchesbody_chunks_/read_mutex_/write_cv_at all. So narrowingmutex_'s scope to just therx_buffer_.append()/ProcessReceivedData()call after the wait doesn't change any protected invariant — it just stops holding an unrelated lock across a blocking wait.Reproduction conditions: most likely to surface with a large/slow upload where the server responds with an error and closes the connection mid-transfer. A fast, small request usually completes before
body_chunks_ever grows enough to need throttling, so the wait — and the deadlock — never triggers. We hit this in practice uploading a large multipart image upload that got rejected mid-transfer by the server; the crash's backtrace symptom varied between occurrences (different apparent fault locations), consistent with a system-wide interrupt-watchdog stall rather than a clean, single-location crash.Test plan
xiaozhi-esp32, ESP32-S3), rebuilt, and confirmed the project still builds and runs normally (HTTP requests, uploads, etc. all continue to work).