Skip to content

Commit cbf6bad

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 cbf6bad

File tree

2 files changed

+122
-3
lines changed

2 files changed

+122
-3
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: 106 additions & 3 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,6 +906,29 @@ esp_err_t esp_websocket_client_destroy(esp_websocket_client_handle_t client)
886906
return ESP_ERR_INVALID_ARG;
887907
}
888908

909+
if (client->status_bits) {
910+
xSemaphoreTakeRecursive(client->lock, portMAX_DELAY);
911+
EventBits_t bits = xEventGroupGetBits(client->status_bits);
912+
if (bits & DESTRUCTION_IN_PROGRESS_BIT) {
913+
xSemaphoreGiveRecursive(client->lock);
914+
ESP_LOGD(TAG, "Destruction already in progress, skipping");
915+
return ESP_OK;
916+
}
917+
918+
if ((bits & STOPPED_BIT) == 0) {
919+
if (xTaskGetCurrentTaskHandle() == client->task_handle) {
920+
ESP_LOGI(TAG, "esp_websocket_client_destroy called from task, deferring...");
921+
client->run = false;
922+
client->selected_for_destroying = true;
923+
xEventGroupSetBits(client->status_bits, DESTRUCTION_IN_PROGRESS_BIT);
924+
xSemaphoreGiveRecursive(client->lock);
925+
return ESP_OK;
926+
}
927+
}
928+
xEventGroupSetBits(client->status_bits, DESTRUCTION_IN_PROGRESS_BIT);
929+
xSemaphoreGiveRecursive(client->lock);
930+
}
931+
889932
if (client->status_bits && (STOPPED_BIT & xEventGroupGetBits(client->status_bits)) == 0) {
890933
stop_wait_task(client);
891934
}
@@ -1118,6 +1161,14 @@ static esp_err_t esp_websocket_client_recv(esp_websocket_client_handle_t client)
11181161

11191162
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);
11201163

1164+
static void esp_websocket_client_destroy_task(void *pv)
1165+
{
1166+
esp_websocket_client_handle_t client = (esp_websocket_client_handle_t)pv;
1167+
ESP_LOGI(TAG, "Deferred destruction of websocket client");
1168+
destroy_and_free_resources(client);
1169+
vTaskDelete(NULL);
1170+
}
1171+
11211172
static void esp_websocket_client_task(void *pv)
11221173
{
11231174
const int lock_timeout = portMAX_DELAY;
@@ -1377,7 +1428,9 @@ static void esp_websocket_client_task(void *pv)
13771428
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
13781429
client->state = WEBSOCKET_STATE_UNKNOW;
13791430
if (client->selected_for_destroying == true) {
1380-
destroy_and_free_resources(client);
1431+
if (xTaskCreate(esp_websocket_client_destroy_task, "ws_destroy", 4096, client, client->config->task_prio, NULL) != pdPASS) {
1432+
ESP_LOGE(TAG, "Failed to create destroy task, memory will leak");
1433+
}
13811434
}
13821435
vTaskDelete(NULL);
13831436
}
@@ -1400,8 +1453,58 @@ esp_err_t esp_websocket_client_start(esp_websocket_client_handle_t client)
14001453
}
14011454
}
14021455

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) {
1456+
client->task_handle = NULL;
1457+
BaseType_t res = pdPASS;
1458+
#if CONFIG_ESP_WS_CLIENT_TASK_STACK_IN_EXT_RAM
1459+
if (client->config->task_stack > 0) {
1460+
if (client->task_stack_buffer == NULL) {
1461+
client->task_stack_buffer = (StackType_t *)heap_caps_calloc(1, client->config->task_stack, MALLOC_CAP_SPIRAM);
1462+
}
1463+
// TCB must be in internal RAM for xTaskCreateStaticPinnedToCore
1464+
if (client->task_buffer == NULL) {
1465+
client->task_buffer = (StaticTask_t *)heap_caps_calloc(1, sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
1466+
}
1467+
1468+
if (client->task_stack_buffer && client->task_buffer) {
1469+
ESP_LOGI(TAG, "Allocated %d bytes stack in PSRAM for WebSocket task", client->config->task_stack);
1470+
client->task_handle = xTaskCreateStaticPinnedToCore(
1471+
esp_websocket_client_task,
1472+
client->config->task_name ? client->config->task_name : "websocket_task",
1473+
client->config->task_stack / sizeof(StackType_t),
1474+
client,
1475+
client->config->task_prio,
1476+
client->task_stack_buffer,
1477+
client->task_buffer,
1478+
tskNO_AFFINITY
1479+
);
1480+
if (client->task_handle == NULL) {
1481+
res = pdFAIL;
1482+
}
1483+
} else {
1484+
ESP_LOGW(TAG, "Failed to allocate PSRAM stack, falling back to internal RAM");
1485+
if (client->task_stack_buffer) {
1486+
heap_caps_free(client->task_stack_buffer);
1487+
client->task_stack_buffer = NULL;
1488+
}
1489+
if (client->task_buffer) {
1490+
heap_caps_free(client->task_buffer);
1491+
client->task_buffer = NULL;
1492+
}
1493+
1494+
res = xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1495+
client->config->task_stack, client, client->config->task_prio, &client->task_handle, tskNO_AFFINITY);
1496+
}
1497+
} else {
1498+
res = xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1499+
client->config->task_stack, client, client->config->task_prio, &client->task_handle, tskNO_AFFINITY);
1500+
}
1501+
#else
1502+
res = xTaskCreatePinnedToCore(esp_websocket_client_task, client->config->task_name ? client->config->task_name : "websocket_task",
1503+
client->config->task_stack / sizeof(StackType_t), client, client->config->task_prio, &client->task_handle, tskNO_AFFINITY);
1504+
#endif
1505+
1506+
if (res != pdPASS || client->task_handle == NULL) {
1507+
client->task_handle = NULL;
14051508
ESP_LOGE(TAG, "Error create websocket task");
14061509
return ESP_FAIL;
14071510
}

0 commit comments

Comments
 (0)