@@ -241,9 +241,29 @@ 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+
261+ if (client -> state == WEBSOCKET_STATE_CLOSING || client -> state == WEBSOCKET_STATE_UNKNOW ||
262+ client -> state == WEBSOCKET_STATE_WAIT_TIMEOUT ) {
263+ ESP_LOGW (TAG , "Connection already closing/closed, skipping abort" );
264+ goto cleanup ;
265+ }
266+
247267 esp_transport_close (client -> transport );
248268
249269 if (!client -> config -> auto_reconnect ) {
@@ -256,6 +276,18 @@ static esp_err_t esp_websocket_client_abort_connection(esp_websocket_client_hand
256276 }
257277 client -> error_handle .error_type = error_type ;
258278 esp_websocket_client_dispatch_event (client , WEBSOCKET_EVENT_DISCONNECTED , NULL , 0 );
279+
280+ cleanup :
281+ if (client -> errormsg_buffer ) {
282+ ESP_LOGD (TAG , "Freeing error buffer (%d bytes) - Free heap: %" PRIu32 " bytes" ,
283+ client -> errormsg_size , esp_get_free_heap_size ());
284+ free (client -> errormsg_buffer );
285+ client -> errormsg_buffer = NULL ;
286+ client -> errormsg_size = 0 ;
287+ } else {
288+ ESP_LOGD (TAG , "Disconnect - Free heap: %" PRIu32 " bytes" , esp_get_free_heap_size ());
289+ }
290+
259291 return ESP_OK ;
260292}
261293
@@ -453,6 +485,8 @@ static void destroy_and_free_resources(esp_websocket_client_handle_t client)
453485 esp_websocket_client_destroy_config (client );
454486 if (client -> transport_list ) {
455487 esp_transport_list_destroy (client -> transport_list );
488+ client -> transport_list = NULL ;
489+ client -> transport = NULL ;
456490 }
457491 vSemaphoreDelete (client -> lock );
458492#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
@@ -671,6 +705,11 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
671705 if (wlen < 0 || (wlen == 0 && need_write != 0 )) {
672706 ret = wlen ;
673707 esp_websocket_free_buf (client , true);
708+
709+ #ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
710+ xSemaphoreGiveRecursive (client -> tx_lock );
711+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY );
712+ #endif
674713 esp_tls_error_handle_t error_handle = esp_transport_get_error_handle (client -> transport );
675714 if (error_handle ) {
676715 esp_websocket_client_error (client , "esp_transport_write() returned %d, transport_error=%s, tls_error_code=%i, tls_flags=%i, errno=%d" ,
@@ -679,8 +718,16 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
679718 } else {
680719 esp_websocket_client_error (client , "esp_transport_write() returned %d, errno=%d" , ret , errno );
681720 }
721+ ESP_LOGD (TAG , "Calling abort_connection due to send error" );
722+ #ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
723+ esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
724+ xSemaphoreGiveRecursive (client -> lock );
725+ return ret ;
726+ #else
727+ // Already holding client->lock, safe to call
682728 esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
683729 goto unlock_and_return ;
730+ #endif
684731 }
685732 opcode = 0 ;
686733 widx += wlen ;
@@ -1019,7 +1066,6 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
10191066 esp_websocket_free_buf (client , false);
10201067 return ESP_OK ;
10211068 }
1022-
10231069 esp_websocket_client_dispatch_event (client , WEBSOCKET_EVENT_DATA , client -> rx_buffer , rlen );
10241070
10251071 client -> payload_offset += rlen ;
@@ -1030,15 +1076,35 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
10301076 const char * data = (client -> payload_len == 0 ) ? NULL : client -> rx_buffer ;
10311077 ESP_LOGD (TAG , "Sending PONG with payload len=%d" , client -> payload_len );
10321078#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
1079+ xSemaphoreGiveRecursive (client -> lock ); // Release client->lock
1080+
1081+ // Now acquire tx_lock with timeout (consistent with PING/CLOSE handling)
10331082 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 ;
1083+ ESP_LOGE (TAG , "Could not lock ws-client within %d timeout for PONG" , WEBSOCKET_TX_LOCK_TIMEOUT_MS );
1084+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY ); // Re-acquire client->lock before returning
1085+ esp_websocket_free_buf (client , false); // Free rx_buffer to prevent memory leak
1086+ return ESP_OK ; // Return gracefully, caller expects client->lock to be held
10361087 }
1037- #endif
1088+
1089+ // Re-acquire client->lock to maintain consistency
1090+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY );
1091+
1092+
1093+ // Another thread may have closed it while we didn't hold client->lock
1094+ if (client -> state == WEBSOCKET_STATE_CLOSING || client -> state == WEBSOCKET_STATE_UNKNOW ||
1095+ client -> state == WEBSOCKET_STATE_WAIT_TIMEOUT || client -> transport == NULL ) {
1096+ ESP_LOGW (TAG , "Transport closed while preparing PONG, skipping send" );
1097+ xSemaphoreGiveRecursive (client -> tx_lock );
1098+ esp_websocket_free_buf (client , false); // Free rx_buffer to prevent memory leak
1099+ return ESP_OK ; // Caller expects client->lock to be held, which it is
1100+ }
1101+
10381102 esp_transport_ws_send_raw (client -> transport , WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN , data , client -> payload_len ,
10391103 client -> config -> network_timeout_ms );
1040- #ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
10411104 xSemaphoreGiveRecursive (client -> tx_lock );
1105+ #else
1106+ esp_transport_ws_send_raw (client -> transport , WS_TRANSPORT_OPCODES_PONG | WS_TRANSPORT_OPCODES_FIN , data , client -> payload_len ,
1107+ client -> config -> network_timeout_ms );
10421108#endif
10431109 } else if (client -> last_opcode == WS_TRANSPORT_OPCODES_PONG ) {
10441110 client -> wait_for_pong_resp = false;
@@ -1136,7 +1202,32 @@ static void esp_websocket_client_task(void *pv)
11361202 client -> state = WEBSOCKET_STATE_CONNECTED ;
11371203 client -> wait_for_pong_resp = false;
11381204 client -> error_handle .error_type = WEBSOCKET_ERROR_TYPE_NONE ;
1205+ client -> payload_len = 0 ;
1206+ client -> payload_offset = 0 ;
1207+ client -> last_fin = false;
1208+ client -> last_opcode = WS_TRANSPORT_OPCODES_NONE ;
1209+
11391210 esp_websocket_client_dispatch_event (client , WEBSOCKET_EVENT_CONNECTED , NULL , 0 );
1211+
1212+ // Check if there is data pending to be read (e.g. piggybacked with handshake)
1213+ if (esp_transport_poll_read (client -> transport , 0 ) > 0 ) {
1214+ esp_err_t recv_result = esp_websocket_client_recv (client );
1215+ if (recv_result == ESP_OK ) {
1216+ xSemaphoreGiveRecursive (client -> lock );
1217+ esp_event_loop_run (client -> event_handle , 0 );
1218+ if (xSemaphoreTakeRecursive (client -> lock , lock_timeout ) != pdPASS ) {
1219+ ESP_LOGE (TAG , "Failed to re-acquire lock after event loop within timeout, retrying with portMAX_DELAY" );
1220+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY );
1221+ }
1222+ if (client -> state != WEBSOCKET_STATE_CONNECTED || client -> transport == NULL ) {
1223+ ESP_LOGD (TAG , "Connection state changed during handshake data processing" );
1224+ break ;
1225+ }
1226+ } else if (recv_result == ESP_FAIL ) {
1227+ ESP_LOGE (TAG , "Error receive data during initial connection" );
1228+ esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
1229+ }
1230+ }
11401231 break ;
11411232 case WEBSOCKET_STATE_CONNECTED :
11421233 if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits (client -> status_bits )) == 0 ) { // only send and check for PING
@@ -1145,8 +1236,23 @@ static void esp_websocket_client_task(void *pv)
11451236 client -> ping_tick_ms = _tick_get_ms ();
11461237 ESP_LOGD (TAG , "Sending PING..." );
11471238#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
1239+ // Release client->lock first to avoid deadlock with send error path
1240+ xSemaphoreGiveRecursive (client -> lock );
1241+
1242+ // Now acquire tx_lock with timeout (consistent with PONG handling)
11481243 if (xSemaphoreTakeRecursive (client -> tx_lock , WEBSOCKET_TX_LOCK_TIMEOUT_MS ) != pdPASS ) {
1149- ESP_LOGE (TAG , "Could not lock ws-client within %d timeout" , WEBSOCKET_TX_LOCK_TIMEOUT_MS );
1244+ ESP_LOGE (TAG , "Could not lock ws-client within %d timeout for PING" , WEBSOCKET_TX_LOCK_TIMEOUT_MS );
1245+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY ); // Re-acquire client->lock before break
1246+ break ;
1247+ }
1248+
1249+ // Re-acquire client->lock to check state
1250+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY );
1251+
1252+ // Another thread may have closed it while we didn't hold client->lock
1253+ if (client -> state != WEBSOCKET_STATE_CONNECTED || client -> transport == NULL ) {
1254+ ESP_LOGW (TAG , "Transport closed while preparing PING, skipping send" );
1255+ xSemaphoreGiveRecursive (client -> tx_lock );
11501256 break ;
11511257 }
11521258#endif
@@ -1182,8 +1288,23 @@ static void esp_websocket_client_task(void *pv)
11821288 if ((CLOSE_FRAME_SENT_BIT & xEventGroupGetBits (client -> status_bits )) == 0 ) {
11831289 ESP_LOGD (TAG , "Closing initiated by the server, sending close frame" );
11841290#ifdef CONFIG_ESP_WS_CLIENT_SEPARATE_TX_LOCK
1291+ // Release client->lock first to avoid deadlock with send error path
1292+ xSemaphoreGiveRecursive (client -> lock );
1293+
1294+ // Now acquire tx_lock with timeout (consistent with PONG/PING handling)
11851295 if (xSemaphoreTakeRecursive (client -> tx_lock , WEBSOCKET_TX_LOCK_TIMEOUT_MS ) != pdPASS ) {
1186- ESP_LOGE (TAG , "Could not lock ws-client within %d timeout" , WEBSOCKET_TX_LOCK_TIMEOUT_MS );
1296+ ESP_LOGE (TAG , "Could not lock ws-client within %d timeout for CLOSE" , WEBSOCKET_TX_LOCK_TIMEOUT_MS );
1297+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY ); // Re-acquire client->lock before break
1298+ break ;
1299+ }
1300+
1301+ // Re-acquire client->lock to check state
1302+ xSemaphoreTakeRecursive (client -> lock , portMAX_DELAY );
1303+
1304+ // Another thread may have closed it while we didn't hold client->lock
1305+ if (client -> state != WEBSOCKET_STATE_CLOSING || client -> transport == NULL ) {
1306+ ESP_LOGW (TAG , "Transport closed while preparing CLOSE frame, skipping send" );
1307+ xSemaphoreGiveRecursive (client -> tx_lock );
11871308 break ;
11881309 }
11891310#endif
@@ -1202,6 +1323,7 @@ static void esp_websocket_client_task(void *pv)
12021323 if (WEBSOCKET_STATE_CONNECTED == client -> state ) {
12031324 read_select = esp_transport_poll_read (client -> transport , 1000 ); //Poll every 1000ms
12041325 if (read_select < 0 ) {
1326+ xSemaphoreTakeRecursive (client -> lock , lock_timeout );
12051327 esp_tls_error_handle_t error_handle = esp_transport_get_error_handle (client -> transport );
12061328 if (error_handle ) {
12071329 esp_websocket_client_error (client , "esp_transport_poll_read() returned %d, transport_error=%s, tls_error_code=%i, tls_flags=%i, errno=%d" ,
@@ -1210,16 +1332,16 @@ static void esp_websocket_client_task(void *pv)
12101332 } else {
12111333 esp_websocket_client_error (client , "esp_transport_poll_read() returned %d, errno=%d" , read_select , errno );
12121334 }
1213- xSemaphoreTakeRecursive (client -> lock , lock_timeout );
12141335 esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
12151336 xSemaphoreGiveRecursive (client -> lock );
12161337 } else if (read_select > 0 ) {
1338+ xSemaphoreTakeRecursive (client -> lock , lock_timeout );
12171339 if (esp_websocket_client_recv (client ) == ESP_FAIL ) {
12181340 ESP_LOGE (TAG , "Error receive data" );
1219- xSemaphoreTakeRecursive ( client -> lock , lock_timeout );
1341+ // Note: Already holding client->lock from line above
12201342 esp_websocket_client_abort_connection (client , WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT );
1221- xSemaphoreGiveRecursive (client -> lock );
12221343 }
1344+ xSemaphoreGiveRecursive (client -> lock );
12231345 } else {
12241346 ESP_LOGV (TAG , "Read poll timeout: skipping esp_transport_poll_read()." );
12251347 }
0 commit comments