Skip to content

Commit 8a75888

Browse files
committed
esp_websocket_client: Add Kconfig options for PSRAM allocation
This commit adds two new Kconfig options: - ESP_WS_CLIENT_TASK_STACK_IN_EXT_RAM: Allows allocating the websocket task stack in PSRAM. - ESP_WS_CLIENT_ALLOC_IN_EXT_RAM: Allows allocating the websocket client structure in PSRAM. These options are useful for memory-constrained applications (like TLS-heavy projects) to save internal RAM. The implementation uses xTaskCreateStaticPinnedToCore for PSRAM stacks and includes a fallback to internal RAM if PSRAM allocation fails. The fix also ensures correct stack depth calculation (words vs bytes) and includes stability fixes for deferred destruction.
1 parent 767a090 commit 8a75888

File tree

2 files changed

+114
-5
lines changed

2 files changed

+114
-5
lines changed

components/esp_websocket_client/Kconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,18 @@ 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+
default n
27+
help
28+
If enabled, the websocket task stack will be allocated in PSRAM using xTaskCreateStatic.
29+
This saves internal RAM but requires PSRAM to be enabled and configured.
30+
31+
config ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
32+
bool "Allocate websocket client structure in PSRAM"
33+
default n
34+
help
35+
If enabled, the websocket client structure and its configuration will be allocated in PSRAM.
36+
This saves internal RAM but requires PSRAM to be enabled and configured.
2337
endmenu

components/esp_websocket_client/esp_websocket_client.c

Lines changed: 100 additions & 5 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;
@@ -747,7 +759,11 @@ static int esp_websocket_client_send_with_exact_opcode(esp_websocket_client_hand
747759

748760
esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_client_config_t *config)
749761
{
762+
#if CONFIG_ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
763+
esp_websocket_client_handle_t client = heap_caps_calloc(1, sizeof(struct esp_websocket_client), MALLOC_CAP_SPIRAM);
764+
#else
750765
esp_websocket_client_handle_t client = calloc(1, sizeof(struct esp_websocket_client));
766+
#endif
751767
ESP_WS_CLIENT_MEM_CHECK(TAG, client, return NULL);
752768

753769
esp_event_loop_args_t event_args = {
@@ -782,7 +798,11 @@ esp_websocket_client_handle_t esp_websocket_client_init(const esp_websocket_clie
782798
ESP_WS_CLIENT_MEM_CHECK(TAG, client->tx_lock, goto _websocket_init_fail);
783799
#endif
784800

801+
#if CONFIG_ESP_WS_CLIENT_ALLOC_IN_EXT_RAM
802+
client->config = heap_caps_calloc(1, sizeof(websocket_config_storage_t), MALLOC_CAP_SPIRAM);
803+
#else
785804
client->config = calloc(1, sizeof(websocket_config_storage_t));
805+
#endif
786806
ESP_WS_CLIENT_MEM_CHECK(TAG, client->config, goto _websocket_init_fail);
787807

788808
if (config->transport == WEBSOCKET_TRANSPORT_OVER_TCP) {
@@ -886,8 +906,22 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
886906
return ESP_ERR_INVALID_ARG;
887907
}
888908

889-
if (client->status_bits && (STOPPED_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
890-
stop_wait_task(client);
909+
if (client->status_bits) {
910+
EventBits_t bits = xEventGroupGetBits(client->status_bits);
911+
if (bits & DESTRUCTION_IN_PROGRESS_BIT) {
912+
ESP_LOGD(TAG, "Destruction already in progress, skipping");
913+
return ESP_OK;
914+
}
915+
916+
if ((bits & STOPPED_BIT) == 0) {
917+
if (stop_wait_task(client) == ESP_FAIL) {
918+
ESP_LOGI(TAG, "esp_websocket_client_destroy called from task, deferring...");
919+
client->run = false;
920+
client->selected_for_destroying = true;
921+
return ESP_OK;
922+
}
923+
}
924+
xEventGroupSetBits(client->status_bits, DESTRUCTION_IN_PROGRESS_BIT);
891925
}
892926

893927
destroy_and_free_resources(client);
@@ -1118,6 +1152,14 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
11181152

11191153
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);
11201154

1155+
static void esp_websocket_client_destroy_task(void *pv)
1156+
{
1157+
esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)pv;
1158+
ESP_LOGI(TAG, "Deferred destruction of websocket client");
1159+
esp_websocket_client_destroy(client);
1160+
vTaskDelete(NULL);
1161+
}
1162+
11211163
static void esp_websocket_client_task(void *pv)
11221164
{
11231165
const int lock_timeout = portMAX_DELAY;
@@ -1377,7 +1419,9 @@ static void esp_websocket_client_task(void *pv)
13771419
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
13781420
client->state = WEBSOCKET_STATE_UNKNOW;
13791421
if (client->selected_for_destroying == true) {
1380-
destroy_and_free_resources(client);
1422+
if (xTaskCreate(esp_websocket_client_destroy_task, "ws_destroy", 4096, client, client->config->task_prio, NULL) != pdPASS) {
1423+
ESP_LOGE(TAG, "Failed to create destroy task, memory will leak");
1424+
}
13811425
}
13821426
vTaskDelete(NULL);
13831427
}
@@ -1400,8 +1444,59 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
14001444
}
14011445
}
14021446

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) {
1447+
client->task_handle = NULL;
1448+
#if CONFIG_ESP_WS_CLIENT_TASK_STACK_IN_EXT_RAM
1449+
if (client->config->task_stack > 0) {
1450+
if (client->task_stack_buffer == NULL) {
1451+
client->task_stack_buffer = (StackType_t *)heap_caps_malloc(client->config->task_stack, MALLOC_CAP_SPIRAM);
1452+
}
1453+
// TCB must be in internal RAM for xTaskCreateStaticPinnedToCore
1454+
if (client->task_buffer == NULL) {
1455+
client->task_buffer = (StaticTask_t *)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
1456+
}
1457+
1458+
if (client->task_stack_buffer && client->task_buffer) {
1459+
ESP_LOGI(TAG, "Allocated %d bytes stack in PSRAM for WebSocket task", client->config->task_stack);
1460+
client->task_handle = xTaskCreateStaticPinnedToCore(
1461+
esp_websocket_client_task,
1462+
client->config->task_name ? client->config->task_name : "websocket_task",
1463+
client->config->task_stack / sizeof(StackType_t),
1464+
client,
1465+
client->config->task_prio,
1466+
client->task_stack_buffer,
1467+
client->task_buffer,
1468+
0 // Core 0 (Network)
1469+
);
1470+
} else {
1471+
ESP_LOGW(TAG, "Failed to allocate PSRAM stack, falling back to internal RAM");
1472+
if (client->task_stack_buffer) {
1473+
heap_caps_free(client->task_stack_buffer);
1474+
client->task_stack_buffer = NULL;
1475+
}
1476+
if (client->task_buffer) {
1477+
heap_caps_free(client->task_buffer);
1478+
client->task_buffer = NULL;
1479+
}
1480+
1481+
if (xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1482+
client->config->task_stack, client, client->config->task_prio, &client->task_handle, 0) != pdPASS) {
1483+
client->task_handle = NULL;
1484+
}
1485+
}
1486+
} else {
1487+
if (xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1488+
client->config->task_stack, client, client->config->task_prio, &client->task_handle, 0) != pdPASS) {
1489+
client->task_handle = NULL;
1490+
}
1491+
}
1492+
#else
1493+
if (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) != pdPASS) {
1495+
client->task_handle = NULL;
1496+
}
1497+
#endif
1498+
1499+
if (client->task_handle == NULL) {
14051500
ESP_LOGE(TAG, "Error create websocket task");
14061501
return ESP_FAIL;
14071502
}

0 commit comments

Comments
 (0)