mirror of
https://github.com/esphome/esphome.git
synced 2026-06-02 11:08:06 +08:00
[web_server_idf] Fix pbuf_free crash by moving shutdown before close (#11995)
This commit is contained in:
committed by
Jonathan Swoboda
parent
0a224f919b
commit
71dc2d374d
@@ -87,6 +87,29 @@ int nonblocking_send(httpd_handle_t hd, int sockfd, const char *buf, size_t buf_
|
|||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void AsyncWebServer::safe_close_with_shutdown(httpd_handle_t hd, int sockfd) {
|
||||||
|
// CRITICAL: Shut down receive BEFORE closing to prevent lwIP race conditions
|
||||||
|
//
|
||||||
|
// The race condition occurs because close() initiates lwIP teardown while
|
||||||
|
// the TCP/IP thread can still receive packets, causing assertions when
|
||||||
|
// recv_tcp() sees partially-torn-down state.
|
||||||
|
//
|
||||||
|
// By shutting down receive first, we tell lwIP to stop accepting new data BEFORE
|
||||||
|
// the teardown begins, eliminating the race window. We only shutdown RD (not RDWR)
|
||||||
|
// to allow the FIN packet to be sent cleanly during close().
|
||||||
|
//
|
||||||
|
// Note: This function may be called with an already-closed socket if the network
|
||||||
|
// stack closed it. In that case, shutdown() will fail but close() is safe to call.
|
||||||
|
//
|
||||||
|
// See: https://github.com/esphome/esphome-webserver/issues/163
|
||||||
|
|
||||||
|
// Attempt shutdown - ignore errors as socket may already be closed
|
||||||
|
shutdown(sockfd, SHUT_RD);
|
||||||
|
|
||||||
|
// Always close - safe even if socket is already closed by network stack
|
||||||
|
close(sockfd);
|
||||||
|
}
|
||||||
|
|
||||||
void AsyncWebServer::end() {
|
void AsyncWebServer::end() {
|
||||||
if (this->server_) {
|
if (this->server_) {
|
||||||
httpd_stop(this->server_);
|
httpd_stop(this->server_);
|
||||||
@@ -115,6 +138,8 @@ void AsyncWebServer::begin() {
|
|||||||
config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
|
config.uri_match_fn = [](const char * /*unused*/, const char * /*unused*/, size_t /*unused*/) { return true; };
|
||||||
// Enable LRU purging if requested (e.g., by captive portal to handle probe bursts)
|
// Enable LRU purging if requested (e.g., by captive portal to handle probe bursts)
|
||||||
config.lru_purge_enable = this->lru_purge_enable_;
|
config.lru_purge_enable = this->lru_purge_enable_;
|
||||||
|
// Use custom close function that shuts down before closing to prevent lwIP race conditions
|
||||||
|
config.close_fn = AsyncWebServer::safe_close_with_shutdown;
|
||||||
if (httpd_start(&this->server_, &config) == ESP_OK) {
|
if (httpd_start(&this->server_, &config) == ESP_OK) {
|
||||||
const httpd_uri_t handler_get = {
|
const httpd_uri_t handler_get = {
|
||||||
.uri = "",
|
.uri = "",
|
||||||
@@ -505,17 +530,11 @@ AsyncEventSourceResponse::AsyncEventSourceResponse(const AsyncWebServerRequest *
|
|||||||
void AsyncEventSourceResponse::destroy(void *ptr) {
|
void AsyncEventSourceResponse::destroy(void *ptr) {
|
||||||
auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
|
auto *rsp = static_cast<AsyncEventSourceResponse *>(ptr);
|
||||||
int fd = rsp->fd_.exchange(0); // Atomically get and clear fd
|
int fd = rsp->fd_.exchange(0); // Atomically get and clear fd
|
||||||
|
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
|
||||||
if (fd > 0) {
|
// Mark as dead - will be cleaned up in the main loop
|
||||||
ESP_LOGD(TAG, "Event source connection closed (fd: %d)", fd);
|
// Note: We don't delete or remove from set here to avoid race conditions
|
||||||
// Immediately shut down the socket to prevent lwIP from delivering more data
|
// httpd will call our custom close_fn (safe_close_with_shutdown) which handles
|
||||||
// This prevents "recv_tcp: recv for wrong pcb!" assertions when the TCP stack
|
// shutdown() before close() to prevent lwIP race conditions
|
||||||
// tries to deliver queued data after the session is marked as dead
|
|
||||||
// See: https://github.com/esphome/esphome/issues/11936
|
|
||||||
shutdown(fd, SHUT_RDWR);
|
|
||||||
// Note: We don't close() the socket - httpd owns it and will close it
|
|
||||||
}
|
|
||||||
// Session will be cleaned up in the main loop to avoid race conditions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper for allowing only unique entries in the queue
|
// helper for allowing only unique entries in the queue
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ class AsyncWebServer {
|
|||||||
static esp_err_t request_handler(httpd_req_t *r);
|
static esp_err_t request_handler(httpd_req_t *r);
|
||||||
static esp_err_t request_post_handler(httpd_req_t *r);
|
static esp_err_t request_post_handler(httpd_req_t *r);
|
||||||
esp_err_t request_handler_(AsyncWebServerRequest *request) const;
|
esp_err_t request_handler_(AsyncWebServerRequest *request) const;
|
||||||
|
static void safe_close_with_shutdown(httpd_handle_t hd, int sockfd);
|
||||||
#ifdef USE_WEBSERVER_OTA
|
#ifdef USE_WEBSERVER_OTA
|
||||||
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
|
esp_err_t handle_multipart_upload_(httpd_req_t *r, const char *content_type);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Reference in New Issue
Block a user