@@ -241,9 +241,28 @@ static esp_err_t esp_websocket_client_dispatch_event(esp_websocket_client_handle
241241 return esp_event_loop_run (client -> event_handle , 0 );
242242}
243243
244+ /**
245+ * @brief Abort the WebSocket connection and initiate reconnection or shutdown
246+ *
247+ * @param client WebSocket client handle
248+ * @param error_type Type of error that caused the abort
249+ *
250+ * @return ESP_OK on success, ESP_FAIL on failure
251+ *
252+ * @note PRECONDITION: client->lock MUST be held by the calling thread before calling this function.
253+ * This function does NOT acquire the lock itself. Calling without the lock will result in
254+ * race conditions and undefined behavior.
255+ */
244256static esp_err_t esp_websocket_client_abort_connection (esp_websocket_client_handle_t client , esp_websocket_error_type_t error_type )
245257{
246258 ESP_WS_CLIENT_STATE_CHECK (TAG , client , return ESP_FAIL );
259+
260+ // CRITICAL: Check if already closing/closed to prevent double-close
261+ if (client -> state == WEBSOCKET_STATE_CLOSING || client -> state == WEBSOCKET_STATE_UNKNOW ) {
262+ ESP_LOGW (TAG , "Connection already closing/closed, skipping abort" );
263+ return ESP_OK ;
264+ }
265+
247266 esp_transport_close (client -> transport );
248267
249268 if (!client -> config -> auto_reconnect ) {
@@ -256,6 +275,17 @@ static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_hand
256275 }
257276 client -> error_handle .error_type = error_type ;
258277 esp_websocket_client_dispatch_event (client , WEBSOCKET_EVENT_DISCONNECTED , NULL , 0 );
278+
279+ if (client -> errormsg_buffer ) {
280+ ESP_LOGD (TAG , "Freeing error buffer (%d bytes) - Free heap: %" PRIu32 " bytes" ,
281+ client -> errormsg_size , esp_get_free_heap_size ());
282+ free (client -> errormsg_buffer );
283+ client -> errormsg_buffer = NULL ;
284+ client -> errormsg_size = 0 ;
285+ } else {
286+ ESP_LOGD (TAG , "Disconnect - Free heap: %" PRIu32 " bytes" , esp_get_free_heap_size ());
287+ }
288+
259289 return ESP_OK ;
260290}
261291
@@ -453,6 +483,8 @@ static void destroy_and_free_resources(esp_websocket_client_handle_t client)
453483 esp_websocket_client_destroy_config (client );
454484 if (client -> transport_list ) {
455485 esp_transport_list_destroy (client -> transport_list );
486+ client -> transport_list = NULL ;
487+ client -> transport = NULL ;
456488 }
457489 vSemaphoreDelete (client -> lock );
458490#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
@@ -679,8 +711,18 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
679711 } else {
680712 esp_websocket_client_error (client , "esp_transport_write() returned %d, errno=%d" , ret , errno );
681713 }
714+ ESP_LOGD (TAG , "Calling abort_connection due to send error" );
715+ #ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
716+ xSemaphoreGiveRecursive (client -> tx_lock );
717+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY );
718+ esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
719+ xSemaphoreGiveRecursive (client -> lock );
720+ return ret ;
721+ #else
722+ // Already holding client->lock, safe to call
682723 esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
683724 goto unlock_and_return ;
725+ #endif
684726 }
685727 opcode = 0 ;
686728 widx += wlen ;
@@ -1019,7 +1061,6 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
10191061 esp_websocket_free_buf (client , false);
10201062 return ESP_OK ;
10211063 }
1022-
10231064 esp_websocket_client_dispatch_event (client , WEBSOCKET_EVENT_DATA , client -> rx_buffer , rlen );
10241065
10251066 client -> payload_offset += rlen ;
@@ -1030,15 +1071,35 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
10301071 const char * data = (client -> payload_len == 0 ) ? NULL : client -> rx_buffer ;
10311072 ESP_LOGD (TAG , "Sending PONG with payload len=%d" , client -> payload_len );
10321073#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
1074+ xSemaphoreGiveRecursive (client -> lock ); // Release client->lock
1075+
1076+ // Now acquire tx_lock with timeout (consistent with PING/CLOSE handling)
10331077 if (xSemaphoreTakeRecursive (client -> tx_lock , WEBSOCKET_TX_LOCK_TIMEOUT_MS ) != pdPASS ) {
1034- ESP_LOGE (TAG , "Could not lock ws-client within %d timeout" , WEBSOCKET_TX_LOCK_TIMEOUT_MS );
1035- return ESP_FAIL ;
1078+ ESP_LOGE (TAG , "Could not lock ws-client within %d timeout for PONG" , WEBSOCKET_TX_LOCK_TIMEOUT_MS );
1079+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY ); // Re-acquire client->lock before returning
1080+ esp_websocket_free_buf (client , false); // Free rx_buffer to prevent memory leak
1081+ return ESP_OK ; // Return gracefully, caller expects client->lock to be held
10361082 }
1037- #endif
1083+
1084+ // Re-acquire client->lock to maintain consistency
1085+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY );
1086+
1087+ // CRITICAL: Check if transport is still valid after re-acquiring lock
1088+ // Another thread may have closed it while we didn't hold client->lock
1089+ if (client -> state == WEBSOCKET_STATE_CLOSING || client -> state == WEBSOCKET_STATE_UNKNOW ||
1090+ client -> state == WEBSOCKET_STATE_WAIT_TIMEOUT || client -> transport == NULL ) {
1091+ ESP_LOGW (TAG , "Transport closed while preparing PONG, skipping send" );
1092+ xSemaphoreGiveRecursive (client -> tx_lock );
1093+ esp_websocket_free_buf (client , false); // Free rx_buffer to prevent memory leak
1094+ return ESP_OK ; // Caller expects client->lock to be held, which it is
1095+ }
1096+
10381097 esp_transport_ws_send_raw (client -> transport , WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN , data , client -> payload_len ,
10391098 client -> config -> network_timeout_ms );
1040- #ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
10411099 xSemaphoreGiveRecursive (client -> tx_lock );
1100+ #else
1101+ esp_transport_ws_send_raw (client -> transport , WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN , data , client -> payload_len ,
1102+ client -> config -> network_timeout_ms );
10421103#endif
10431104 } else if (client -> last_opcode == WS_TRANSPORT_OPCODES_PONG ) {
10441105 client -> wait_for_pong_resp = false;
@@ -1136,6 +1197,11 @@ static void esp_websocket_client_task(void *pv)
11361197 client -> state = WEBSOCKET_STATE_CONNECTED ;
11371198 client -> wait_for_pong_resp = false;
11381199 client -> error_handle .error_type = WEBSOCKET_ERROR_TYPE_NONE ;
1200+ client -> payload_len = 0 ;
1201+ client -> payload_offset = 0 ;
1202+ client -> last_fin = false;
1203+ client -> last_opcode = WS_TRANSPORT_OPCODES_NONE ;
1204+
11391205 esp_websocket_client_dispatch_event (client , WEBSOCKET_EVENT_CONNECTED , NULL , 0 );
11401206 break ;
11411207 case WEBSOCKET_STATE_CONNECTED :
@@ -1221,12 +1287,15 @@ static void esp_websocket_client_task(void *pv)
12211287 esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
12221288 xSemaphoreGiveRecursive (client -> lock );
12231289 } else if (read_select > 0 ) {
1290+ // CRITICAL: Protect entire recv operation with client->lock
1291+ // This prevents transport from being closed while recv is in progress
1292+ xSemaphoreTakeRecursive (client -> lock , lock_timeout );
12241293 if (esp_websocket_client_recv (client ) == ESP_FAIL ) {
12251294 ESP_LOGE (TAG , "Error receive data" );
1226- xSemaphoreTakeRecursive ( client -> lock , lock_timeout );
1295+ // Note: Already holding client->lock from line above
12271296 esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
1228- xSemaphoreGiveRecursive (client -> lock );
12291297 }
1298+ xSemaphoreGiveRecursive (client -> lock );
12301299 }
12311300 } else if (WEBSOCKET_STATE_WAIT_TIMEOUT == client -> state ) {
12321301 // waiting for reconnection or a request to stop the client...
0 commit comments