Skip to content

Commit 9d9f1c9

Browse files
committed
esp_websocket_client: Add Kconfig options for PSRAM allocation
- Added CONFIG_ESP_WS_CLIENT_TASK_STACK_IN_EXT_RAM to allow allocating WebSocket task stack in PSRAM. - Added CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER to allow dynamic allocation of RX/TX buffers. - Fixed memory leak in esp_websocket_client_destroy by deferring destruction to a separate task when called from the WebSocket task itself. - Fixed stack size calculation for xTaskCreateStaticPinnedToCore (expecting words). - Fixed race conditions and use-after-free in task destruction by using vTaskSuspend + vTaskDelete pattern instead of self-deletion. - Fixed double-free race condition in destroy_on_exit where manual destroy could race with deferred destruction.
1 parent 767a090 commit 9d9f1c9

File tree

2 files changed

+153
-6
lines changed

2 files changed

+153
-6
lines changed

components/esp_websocket_client/Kconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,20 @@ menu "ESP WebSocket client"
2020
default 2000
2121
help
2222
Timeout for acquiring the TX lock when using separate TX lock.
23+
24+
config ESP_WS_CLIENT_TASK_STACK_IN_EXT_RAM
25+
bool "Allocate websocket task stack in PSRAM"
26+
depends on SPIRAM
27+
default n
28+
help
29+
If enabled, the websocket task stack will be allocated in PSRAM using xTaskCreateStatic.
30+
This saves internal RAM but requires PSRAM to be enabled and configured.
31+
32+
config ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
33+
bool "Allocate websocket client structure in PSRAM"
34+
depends on SPIRAM
35+
default n
36+
help
37+
If enabled, the websocket client structure and its configuration will be allocated in PSRAM.
38+
This saves internal RAM but requires PSRAM to be enabled and configured.
2339
endmenu

components/esp_websocket_client/esp_websocket_client.c

Lines changed: 137 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const static int STOPPED_BIT = BIT0;
7575
const static int CLOSE_FRAME_SENT_BIT = BIT1; // Indicates that a close frame was sent by the client
7676
// and we are waiting for the server to continue with clean close
7777
const static int REQUESTED_STOP_BIT = BIT2; // Indicates that a client stop has been requested
78+
const static int DESTRUCTION_IN_PROGRESS_BIT = BIT3; // Indicates that the client is being destroyed
7879

7980
ESP_EVENT_DEFINE_BASE(WEBSOCKET_EVENTS);
8081

@@ -156,6 +157,8 @@ struct esp_websocket_client {
156157
int payload_offset;
157158
esp_transport_keep_alive_t keep_alive_cfg;
158159
struct ifreq *if_name;
160+
StackType_t *task_stack_buffer;
161+
StaticTask_t *task_buffer;
159162
};
160163

161164
static uint64_t _tick_get_ms(void)
@@ -497,6 +500,15 @@ static void destroy_and_free_resources(esp_websocket_client_handle_t client)
497500
free(client->errormsg_buffer);
498501
if (client->status_bits) {
499502
vEventGroupDelete(client->status_bits);
503+
client->status_bits = NULL;
504+
}
505+
if (client->task_stack_buffer) {
506+
heap_caps_free(client->task_stack_buffer);
507+
client->task_stack_buffer = NULL;
508+
}
509+
if (client->task_buffer) {
510+
heap_caps_free(client->task_buffer);
511+
client->task_buffer = NULL;
500512
}
501513
free(client);
502514
client = NULL;
@@ -514,11 +526,18 @@ static esp_err_t stop_wait_task(esp_websocket_client_handle_t client)
514526
client->run = false;
515527
xEventGroupSetBits(client->status_bits, REQUESTED_STOP_BIT);
516528
xEventGroupWaitBits(client->status_bits, STOPPED_BIT, false, true, portMAX_DELAY);
529+
530+
if (client->task_handle) {
531+
while (eTaskGetState(client->task_handle) != eSuspended) {
532+
vTaskDelay(1);
533+
}
534+
vTaskDelete(client->task_handle);
535+
client->task_handle = NULL;
536+
}
537+
517538
client->state = WEBSOCKET_STATE_UNKNOW;
518539
return ESP_OK;
519540
}
520-
521-
#if WS_TRANSPORT_HEADER_CALLBACK_SUPPORT
522541
static void websocket_header_hook(void * client, const char * line, int line_len)
523542
{
524543
ESP_LOGD(TAG, "%s header:%.*s", __func__, line_len, line);
@@ -747,7 +766,11 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
747766

748767
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config)
749768
{
769+
#if CONFIG_ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
770+
esp_websocket_client_handle_t client = heap_caps_calloc(1, sizeof(struct esp_websocket_client), MALLOC_CAP_SPIRAM);
771+
#else
750772
esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client));
773+
#endif
751774
ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL);
752775

753776
esp_event_loop_args_t event_args = {
@@ -782,7 +805,11 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
782805
ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_lock, goto _websocket_init_fail);
783806
#endif
784807

808+
#if CONFIG_ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
809+
client->config = heap_caps_calloc(1, sizeof(websocket_config_storage_t), MALLOC_CAP_SPIRAM);
810+
#else
785811
client->config = calloc(1, sizeof(websocket_config_storage_t));
812+
#endif
786813
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);
787814

788815
if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
@@ -886,6 +913,29 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
886913
return ESP_ERR_INVALID_ARG;
887914
}
888915

916+
if (client->status_bits) {
917+
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
918+
EventBits_t bits = xEventGroupGetBits(client->status_bits);
919+
if (bits & DESTRUCTION_IN_PROGRESS_BIT) {
920+
xSemaphoreGiveRecursive(client->lock);
921+
ESP_LOGD(TAG, "Destruction already in progress, skipping");
922+
return ESP_OK;
923+
}
924+
925+
if ((bits & STOPPED_BIT) == 0) {
926+
if (xTaskGetCurrentTaskHandle() == client->task_handle) {
927+
ESP_LOGI(TAG, "esp_websocket_client_destroy called from task, deferring...");
928+
client->run = false;
929+
client->selected_for_destroying = true;
930+
xEventGroupSetBits(client->status_bits, DESTRUCTION_IN_PROGRESS_BIT);
931+
xSemaphoreGiveRecursive(client->lock);
932+
return ESP_OK;
933+
}
934+
}
935+
xEventGroupSetBits(client->status_bits, DESTRUCTION_IN_PROGRESS_BIT);
936+
xSemaphoreGiveRecursive(client->lock);
937+
}
938+
889939
if (client->status_bits && (STOPPED_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
890940
stop_wait_task(client);
891941
}
@@ -1118,6 +1168,23 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
11181168

11191169
static int esp_websocket_client_send_close(esp_websocket_client_handle_t client, int code, const char *additional_data, int total_len, TickType_t timeout);
11201170

1171+
static void esp_websocket_client_destroy_task(void *pv)
1172+
{
1173+
esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)pv;
1174+
ESP_LOGI(TAG, "Deferred destruction of websocket client");
1175+
1176+
if (client->task_handle) {
1177+
while (eTaskGetState(client->task_handle) != eSuspended) {
1178+
vTaskDelay(1);
1179+
}
1180+
vTaskDelete(client->task_handle);
1181+
client->task_handle = NULL;
1182+
}
1183+
1184+
destroy_and_free_resources(client);
1185+
vTaskDelete(NULL);
1186+
}
1187+
11211188
static void esp_websocket_client_task(void *pv)
11221189
{
11231190
const int lock_timeout = portMAX_DELAY;
@@ -1377,9 +1444,23 @@ static void esp_websocket_client_task(void *pv)
13771444
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
13781445
client->state = WEBSOCKET_STATE_UNKNOW;
13791446
if (client->selected_for_destroying == true) {
1380-
destroy_and_free_resources(client);
1447+
bool already_destroying = false;
1448+
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
1449+
if (xEventGroupGetBits(client->status_bits) & DESTRUCTION_IN_PROGRESS_BIT) {
1450+
already_destroying = true;
1451+
} else {
1452+
xEventGroupSetBits(client->status_bits, DESTRUCTION_IN_PROGRESS_BIT);
1453+
}
1454+
xSemaphoreGiveRecursive(client->lock);
1455+
1456+
if (!already_destroying) {
1457+
if (xTaskCreate(esp_websocket_client_destroy_task, "ws_destroy", 4096, client, client->config->task_prio, NULL) != pdPASS) {
1458+
ESP_LOGE(TAG, "Failed to create destroy task, memory will leak");
1459+
xEventGroupClearBits(client->status_bits, DESTRUCTION_IN_PROGRESS_BIT);
1460+
}
1461+
}
13811462
}
1382-
vTaskDelete(NULL);
1463+
vTaskSuspend(NULL);
13831464
}
13841465

13851466
esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
@@ -1400,8 +1481,58 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
14001481
}
14011482
}
14021483

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) {
1484+
client->task_handle = NULL;
1485+
BaseType_t res = pdPASS;
1486+
#if CONFIG_ESP_WS_CLIENT_TASK_STACK_IN_EXT_RAM
1487+
if (client->config->task_stack > 0) {
1488+
if (client->task_stack_buffer == NULL) {
1489+
client->task_stack_buffer = (StackType_t *)heap_caps_calloc(1, client->config->task_stack, MALLOC_CAP_SPIRAM);
1490+
}
1491+
// TCB must be in internal RAM for xTaskCreateStaticPinnedToCore
1492+
if (client->task_buffer == NULL) {
1493+
client->task_buffer = (StaticTask_t *)heap_caps_calloc(1, sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
1494+
}
1495+
1496+
if (client->task_stack_buffer && client->task_buffer) {
1497+
ESP_LOGI(TAG, "Allocated %d bytes stack in PSRAM for WebSocket task", client->config->task_stack);
1498+
client->task_handle = xTaskCreateStaticPinnedToCore(
1499+
esp_websocket_client_task,
1500+
client->config->task_name ? client->config->task_name : "websocket_task",
1501+
client->config->task_stack / sizeof(StackType_t),
1502+
client,
1503+
client->config->task_prio,
1504+
client->task_stack_buffer,
1505+
client->task_buffer,
1506+
tskNO_AFFINITY
1507+
);
1508+
if (client->task_handle == NULL) {
1509+
res = pdFAIL;
1510+
}
1511+
} else {
1512+
ESP_LOGW(TAG, "Failed to allocate PSRAM stack, falling back to internal RAM");
1513+
if (client->task_stack_buffer) {
1514+
heap_caps_free(client->task_stack_buffer);
1515+
client->task_stack_buffer = NULL;
1516+
}
1517+
if (client->task_buffer) {
1518+
heap_caps_free(client->task_buffer);
1519+
client->task_buffer = NULL;
1520+
}
1521+
1522+
res = xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1523+
client->config->task_stack, client, client->config->task_prio, &client->task_handle, tskNO_AFFINITY);
1524+
}
1525+
} else {
1526+
res = xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1527+
client->config->task_stack, client, client->config->task_prio, &client->task_handle, tskNO_AFFINITY);
1528+
}
1529+
#else
1530+
res = xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1531+
client->config->task_stack, client, client->config->task_prio, &client->task_handle, tskNO_AFFINITY);
1532+
#endif
1533+
1534+
if (res != pdPASS || client->task_handle == NULL) {
1535+
client->task_handle = NULL;
14051536
ESP_LOGE(TAG, "Error create websocket task");
14061537
return ESP_FAIL;
14071538
}

0 commit comments

Comments
 (0)