Skip to content

Commit 13277dd

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 13277dd

File tree

3 files changed

+120
-5
lines changed

3 files changed

+120
-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: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

161164
static 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

748772
esp_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

11191156
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);
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+
11211173
static 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
}

components/mdns/mdns_service.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ static esp_err_t create_task_with_caps(void)
261261
s_stack_buffer = mdns_mem_task_malloc(MDNS_SERVICE_STACK_DEPTH);
262262
ESP_GOTO_ON_FALSE(s_stack_buffer != NULL, ESP_FAIL, alloc_failed, TAG, "failed to allocate memory for the mDNS task's stack");
263263

264-
s_service_task_handle = xTaskCreateStaticPinnedToCore(service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, MDNS_TASK_PRIORITY, s_stack_buffer, &mdns_task_buffer, MDNS_TASK_AFFINITY);
264+
s_service_task_handle = xTaskCreateStaticPinnedToCore(service_task, "mdns", MDNS_SERVICE_STACK_DEPTH / sizeof(StackType_t), NULL, MDNS_TASK_PRIORITY, s_stack_buffer, &mdns_task_buffer, MDNS_TASK_AFFINITY);
265265
ESP_GOTO_ON_FALSE(s_service_task_handle != NULL, ESP_FAIL, err, TAG, "failed to create task for the mDNS");
266266

267267
return ret;

0 commit comments

Comments
 (0)