@@ -156,6 +156,9 @@ struct esp_websocket_client {
156156 int payload_offset ;
157157 esp_transport_keep_alive_t keep_alive_cfg ;
158158 struct ifreq * if_name ;
159+ StackType_t * task_stack_buffer ;
160+ StaticTask_t * task_buffer ;
161+ esp_timer_handle_t destroy_timer ;
159162};
160163
161164static uint64_t _tick_get_ms (void )
@@ -498,6 +501,27 @@ static void destroy_and_free_resources(esp_websocket_client_handle_t client)
498501 if (client -> status_bits ) {
499502 vEventGroupDelete (client -> status_bits );
500503 }
504+ if (client -> destroy_timer ) {
505+ esp_timer_stop (client -> destroy_timer );
506+ // If we are in the timer callback, we cannot delete the timer itself
507+ // as it would return ESP_ERR_INVALID_STATE.
508+ // However, the timer is a one-shot timer and will be cleaned up by the system
509+ // if we don't delete it here, but to be safe and avoid leaks in other paths,
510+ // we only delete it if we are NOT in the timer context.
511+ // Since we don't have a clean way to check "is_in_timer_callback",
512+ // and this is a one-shot timer, we can just delete it and ignore the error,
513+ // or better: the timer handle is part of the client struct which is about to be freed.
514+ esp_timer_delete (client -> destroy_timer );
515+ client -> destroy_timer = NULL ;
516+ }
517+ if (client -> task_stack_buffer ) {
518+ heap_caps_free (client -> task_stack_buffer );
519+ client -> task_stack_buffer = NULL ;
520+ }
521+ if (client -> task_buffer ) {
522+ heap_caps_free (client -> task_buffer );
523+ client -> task_buffer = NULL ;
524+ }
501525 free (client );
502526 client = NULL ;
503527}
@@ -747,7 +771,11 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
747771
748772esp_websocket_client_handle_t esp_websocket_client_init (const esp_websocket_client_config_t * config )
749773{
774+ #if CONFIG_ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
775+ esp_websocket_client_handle_t client = heap_caps_calloc (1 , sizeof (struct esp_websocket_client ), MALLOC_CAP_SPIRAM );
776+ #else
750777 esp_websocket_client_handle_t client = calloc (1 , sizeof (struct esp_websocket_client ));
778+ #endif
751779 ESP_WS_CLIENT_MEM_CHECK (TAG , client , return NULL );
752780
753781 esp_event_loop_args_t event_args = {
@@ -782,7 +810,11 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
782810 ESP_WS_CLIENT_MEM_CHECK (TAG , client -> tx_lock , goto _websocket_init_fail );
783811#endif
784812
813+ #if CONFIG_ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
814+ client -> config = heap_caps_calloc (1 , sizeof (websocket_config_storage_t ), MALLOC_CAP_SPIRAM );
815+ #else
785816 client -> config = calloc (1 , sizeof (websocket_config_storage_t ));
817+ #endif
786818 ESP_WS_CLIENT_MEM_CHECK (TAG , client -> config , goto _websocket_init_fail );
787819
788820 if (config -> transport == WEBSOCKET_TRANSPORT_OVER_TCP ) {
@@ -887,7 +919,12 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
887919 }
888920
889921 if (client -> status_bits && (STOPPED_BIT & xEventGroupGetBits (client -> status_bits )) == 0 ) {
890- stop_wait_task (client );
922+ if (stop_wait_task (client ) == ESP_FAIL ) {
923+ ESP_LOGI (TAG , "esp_websocket_client_destroy called from task, deferring..." );
924+ client -> run = false;
925+ client -> selected_for_destroying = true;
926+ return ESP_OK ;
927+ }
891928 }
892929
893930 destroy_and_free_resources (client );
@@ -1118,6 +1155,21 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
11181155
11191156static int esp_websocket_client_send_close (esp_websocket_client_handle_t client , int code , const char * additional_data , int total_len , TickType_t timeout );
11201157
1158+ static void esp_websocket_client_destroy_timer_cb (void * arg )
1159+ {
1160+ esp_websocket_client_handle_t client = (esp_websocket_client_handle_t )arg ;
1161+ ESP_LOGI (TAG , "Deferred destruction of websocket client" );
1162+
1163+ // The timer is one-shot and has finished. We can delete it now
1164+ // before calling destroy, which would otherwise fail to delete it
1165+ // from within this callback.
1166+ esp_timer_handle_t timer = client -> destroy_timer ;
1167+ client -> destroy_timer = NULL ;
1168+ esp_timer_delete (timer );
1169+
1170+ esp_websocket_client_destroy (client );
1171+ }
1172+
11211173static void esp_websocket_client_task (void * pv )
11221174{
11231175 const int lock_timeout = portMAX_DELAY ;
@@ -1377,7 +1429,16 @@ static void esp_websocket_client_task(void *pv)
13771429 xEventGroupSetBits (client -> status_bits , STOPPED_BIT );
13781430 client -> state = WEBSOCKET_STATE_UNKNOW ;
13791431 if (client -> selected_for_destroying == true) {
1380- destroy_and_free_resources (client );
1432+ esp_timer_create_args_t timer_args = {
1433+ .callback = esp_websocket_client_destroy_timer_cb ,
1434+ .arg = client ,
1435+ .name = "ws_destroy"
1436+ };
1437+ if (esp_timer_create (& timer_args , & client -> destroy_timer ) == ESP_OK ) {
1438+ esp_timer_start_once (client -> destroy_timer , 100 * 1000 ); // 100ms
1439+ } else {
1440+ ESP_LOGE (TAG , "Failed to create destroy timer, memory will leak" );
1441+ }
13811442 }
13821443 vTaskDelete (NULL );
13831444}
@@ -1400,8 +1461,48 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
14001461 }
14011462 }
14021463
1403- if (xTaskCreate (esp_websocket_client_task , client -> config -> task_name ? client -> config -> task_name : "websocket_task" ,
1404- client -> config -> task_stack , client , client -> config -> task_prio , & client -> task_handle ) != pdTRUE ) {
1464+ #if CONFIG_ESP_WS_CLIENT_TASK_STACK_IN_EXT_RAM
1465+ if (client -> config -> task_stack > 0 ) {
1466+ if (client -> task_stack_buffer == NULL ) {
1467+ client -> task_stack_buffer = (StackType_t * )heap_caps_malloc (client -> config -> task_stack , MALLOC_CAP_SPIRAM );
1468+ }
1469+ // TCB must be in internal RAM for xTaskCreateStaticPinnedToCore
1470+ if (client -> task_buffer == NULL ) {
1471+ client -> task_buffer = (StaticTask_t * )heap_caps_malloc (sizeof (StaticTask_t ), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT );
1472+ }
1473+
1474+ if (client -> task_stack_buffer && client -> task_buffer ) {
1475+ ESP_LOGI (TAG , "Allocated %d bytes stack in PSRAM for WebSocket task" , client -> config -> task_stack );
1476+ client -> task_handle = xTaskCreateStaticPinnedToCore (
1477+ esp_websocket_client_task ,
1478+ client -> config -> task_name ? client -> config -> task_name : "websocket_task" ,
1479+ client -> config -> task_stack / sizeof (StackType_t ),
1480+ client ,
1481+ client -> config -> task_prio ,
1482+ client -> task_stack_buffer ,
1483+ client -> task_buffer ,
1484+ 0 // Core 0 (Network)
1485+ );
1486+ } else {
1487+ ESP_LOGW (TAG , "Failed to allocate PSRAM stack, falling back to internal RAM" );
1488+ if (client -> task_stack_buffer ) heap_caps_free (client -> task_stack_buffer );
1489+ if (client -> task_buffer ) heap_caps_free (client -> task_buffer );
1490+ client -> task_stack_buffer = NULL ;
1491+ client -> task_buffer = NULL ;
1492+
1493+ xTaskCreatePinnedToCore (esp_websocket_client_task , client -> config -> task_name ? client -> config -> task_name : "websocket_task" ,
1494+ client -> config -> task_stack , client , client -> config -> task_prio , & client -> task_handle , 0 );
1495+ }
1496+ } else {
1497+ xTaskCreatePinnedToCore (esp_websocket_client_task , client -> config -> task_name ? client -> config -> task_name : "websocket_task" ,
1498+ client -> config -> task_stack , client , client -> config -> task_prio , & client -> task_handle , 0 );
1499+ }
1500+ #else
1501+ xTaskCreatePinnedToCore (esp_websocket_client_task , client -> config -> task_name ? client -> config -> task_name : "websocket_task" ,
1502+ client -> config -> task_stack , client , client -> config -> task_prio , & client -> task_handle , 0 );
1503+ #endif
1504+
1505+ if (client -> task_handle == NULL ) {
14051506 ESP_LOGE (TAG , "Error create websocket task" );
14061507 return ESP_FAIL ;
14071508 }
0 commit comments