esp_http_server: logging can clobber errno and turn EAGAIN into a fatal send error (IDFGH-18290)
Answers checklist.
- I have read the relevant ESP-IDF Programming Guide documentation and the issue is not addressed there.
- I have updated my IDF branch to the latest version and checked that the issue is present there.
- I have searched the issue tracker and not found a report of this errno-clobbering issue.
Tested on v6.1. The same log-before-switch code is present in upstream master, but I haven't rerun the firmware on master.
IDF version.
v6.1 (fff9895c82d744c7237be8847347bdd1b07c6643)
Espressif SoC revision.
ESP32-S3, revision v0.2
Operating System used.
macOS (Apple Silicon)
How did you build your project?
Command line with idf.py
Development Kit.
ESP32-S3-DevKitC-1 N16R8
Power Supply used.
USB
What is the expected behavior?
When a nonblocking send would block, httpd_socket_send() should return HTTPD_SOCK_ERR_TIMEOUT, allowing the application to retry.
What is the actual behavior?
It returns HTTPD_SOCK_ERR_FAIL. My SSE server consequently closes healthy clients, which reconnect every ~3 seconds. This starts when the backlog exceeds the 5,760-byte TCP send buffer; smaller backlogs work.
Steps to reproduce.
Use UART as the primary console and USB Serial/JTAG as the secondary console, with the secondary USB connection absent:
CONFIG_ESP_CONSOLE_UART_DEFAULT=y CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760With warning logs enabled, send an SSE backlog larger than the TCP send buffer using
httpd_socket_send(server, fd, data, remaining, MSG_DONTWAIT), advancing by each successful partial write.When the buffer fills, inspect
errnobefore and after the warning inhttpd_sock_err().
Debug Logs.
Observed values from instrumentation (summarized, not a raw log):
Bytes sent before failure: 5760
Underlying send(): -1, errno = 11 (EAGAIN)
Warning prints: error in send : 11
errno at the following switch: 5 (EIO)
httpd_socket_send(): HTTPD_SOCK_ERR_FAILMore Information.
In httpd_sock_err(), ESP_LOGW(..., errno) runs before switch (errno).
The console writes to both outputs. The secondary usb_serial_jtag_write() sets errno = EIO when disconnected, and the console writer doesn't restore it. The warning can therefore print EAGAIN over UART while leaving EIO for the switch.
Calling send() directly and immediately capturing errno, then retrying on EAGAIN/EWOULDBLOCK/EINTR, fixed the issue: a forced ~24 KB replay delivered all 25,427 bytes without disconnecting.
Could httpd_sock_err() save errno before logging and use that saved value for both the warning and the switch? The helper is shared by the default send and receive paths.
Source: espressif/esp-idf