WLED pins Aircoookie/ESPAsyncWebServer at ac44e32. In AsyncAbstractResponse::_ack() (WebResponses.cpp), the RESPONSE_WAIT_ACK exit condition:
} else if (_state == RESPONSE_WAIT_ACK) {
if (!_sendContentLength || _ackedLength >= _writtenLength) {
_state = RESPONSE_END;
if (!_chunked && !_sendContentLength)
request->client()->close(true);
}
}
On the chunked path (_sendContentLength=false), !_sendContentLength is unconditionally true — close() fires as soon as the final zero-length chunk lands in the lwIP send buffer, not when it's ACKed. On fast WiFi the gap is negligible. On slow or high-latency transports (PPP/UART, congested WiFi with retransmissions) the response is truncated before the tail bytes drain.
AsyncBasicResponse::_ack() in the same file already uses _ackedLength >= _writtenLength unconditionally (lines 320-323). AsyncAbstractResponse was never updated to match.
Fork comparison
| Fork |
AsyncAbstractResponse WAIT_ACK condition |
Status |
Aircoookie ac44e32 (pinned) |
!_sendContentLength || _ackedLength >= _writtenLength |
Buggy |
me-no-dev AsyncAbstractResponse |
!_sendContentLength || _ackedLength >= _writtenLength |
Buggy |
me-no-dev AsyncBasicResponse lines 320-323 |
_ackedLength >= _writtenLength |
Fixed — inconsistent in same file |
| mathieucarbou |
!_sendContentLength || _ackedLength >= _writtenLength |
Buggy |
ESP32Async 747223f02f8e |
Unconditional _state = RESPONSE_END |
Addressed — relies on _onAck for graceful close |
Workaround
#5808 avoids the buggy path at the WLED level by switching respondModeData() and respondModeNames() from sendChunked() to request->send() with a pre-computed Content-Length. Not a fix — other endpoints using sendChunked() remain exposed.
Fix options
Match AsyncBasicResponse — drop the !_sendContentLength shortcut:
} else if (_state == RESPONSE_WAIT_ACK) {
if (_ackedLength >= _writtenLength) {
_state = RESPONSE_END;
}
}
Or pull the ESP32Async approach (747223f02f8e) into the Aircoookie pin.
Discussion filed at ESP32Async: ESP32Async/ESPAsyncWebServer#472
WLED pins
Aircoookie/ESPAsyncWebServeratac44e32. InAsyncAbstractResponse::_ack()(WebResponses.cpp), theRESPONSE_WAIT_ACKexit condition:On the chunked path (
_sendContentLength=false),!_sendContentLengthis unconditionally true —close()fires as soon as the final zero-length chunk lands in the lwIP send buffer, not when it's ACKed. On fast WiFi the gap is negligible. On slow or high-latency transports (PPP/UART, congested WiFi with retransmissions) the response is truncated before the tail bytes drain.AsyncBasicResponse::_ack()in the same file already uses_ackedLength >= _writtenLengthunconditionally (lines 320-323).AsyncAbstractResponsewas never updated to match.Fork comparison
AsyncAbstractResponseWAIT_ACK conditionac44e32(pinned)!_sendContentLength || _ackedLength >= _writtenLengthAsyncAbstractResponse!_sendContentLength || _ackedLength >= _writtenLengthAsyncBasicResponselines 320-323_ackedLength >= _writtenLength!_sendContentLength || _ackedLength >= _writtenLength747223f02f8e_state = RESPONSE_END_onAckfor graceful closeWorkaround
#5808 avoids the buggy path at the WLED level by switching
respondModeData()andrespondModeNames()fromsendChunked()torequest->send()with a pre-computedContent-Length. Not a fix — other endpoints usingsendChunked()remain exposed.Fix options
Match
AsyncBasicResponse— drop the!_sendContentLengthshortcut:Or pull the ESP32Async approach (
747223f02f8e) into the Aircoookie pin.Discussion filed at ESP32Async: ESP32Async/ESPAsyncWebServer#472