Skip to content

cpu/esp32: fix of a serious memory leak when using esp_wifi on ESP32-S3#21974

Merged
benpicco merged 3 commits intoRIOT-OS:masterfrom
gschorcht:cpu/esp32/fix_esp32s3_systimer_problem
Jan 12, 2026
Merged

cpu/esp32: fix of a serious memory leak when using esp_wifi on ESP32-S3#21974
benpicco merged 3 commits intoRIOT-OS:masterfrom
gschorcht:cpu/esp32/fix_esp32s3_systimer_problem

Conversation

@gschorcht
Copy link
Copy Markdown
Contributor

@gschorcht gschorcht commented Jan 9, 2026

Contribution description

This PR fixes a serious memory leak on ESP32-S3 and ESP32-S2.

When investigating the problem described in issue #21923, I realized coincidentally that esp_wifi causes a serious memory leak on ESP32-S3 and ESP32-S2 that is related to the SYSTIMER peripheral.

Background

The implementation of the ESP-IDF module esp_wifi uses the ESP-IDF module esp_timer to track the PLL of the WiFi modem when the PHY layer is briefly enabled and disabled again at regular intervals. To do this, it starts a timer when the PHY layer is enabled and stops the timer when the PHY layer is disabled again.

To prevent the corresponding timer object from being deleted when the timer is stopped in ISR context, the timer object is not deleted directly when the timer is stopped, but is added to a list of timer objects to be deleted. The timer objects in this list should then be deleted by the esp_wifi task in the thread context. To be executed periodically, esp_wifi task uses the SYSTIMER peripheral on ESP32x SoCs with the exception of ESP32.

On multi-core SoCs however, this SYSTIMER peripheral is stalled if one of the CPUs is stalled. Since RIOT generally uses only one CPU, the SYSTIMER peripheral doesn't work in RIOT. As a result, the esp_wifi task never executed and the timer objects are never deleted. This causes a serious memory leak on ESP32-S3 because the list of timer objects to be deleted grows rapidly within a couple of seconds.

Defining portNUM_PROCESSORS always as 1 (which reflects the reality) solves this problem because stalling the SYSTIMER peripheral when the second CPU is stalled isn't enabled in this case.

As a side effect it also solves the same memory leak on ESP32-S2 even though it is a single-core SoC.

All other ESP32x SoC didn't have this problem.

Testing procedure

Compile and flash the networking example with heap command for ESP32-S3 and ESP32-S2:

CFLAGS='-DWIFI_SSID=\"SSID\" -DWIFI_PASS=\"PASS\"' USEMODULE='shell_cmd_heap esp_wifi' \
BOARD=esp32s3-devkit make -C examples/networking/gnrc/networking flash term

Without PR, the allocated memory increases by several hundred bytes every second.

2026-01-09 15:00:38,833 # RIOT network stack example application
2026-01-09 15:00:38,835 # All up, running the shell now
2026-01-09 15:00:41,279 # WiFi connected to ssid BSHS1, channel 1
> heap
2026-01-09 15:01:00,650 # heap
2026-01-09 15:01:00,651 # heap: 340780 (used 98428, free 242352) [bytes]
> heap
2026-01-09 15:01:10,413 # heap
2026-01-09 15:01:10,424 # heap: 340516 (used 100540, free 239976) [bytes]
> heap
2026-01-09 15:01:20,320 # heap
2026-01-09 15:01:20,322 # heap: 340132 (used 103616, free 236516) [bytes]
> heap
2026-01-09 15:01:30,595 # heap
2026-01-09 15:01:30,596 # heap: 339728 (used 106952, free 232776) [bytes]

With PR, the allocated memory should stabilize after a few seconds and only increase or decrease slightly.

2026-01-09 15:09:24,803 # RIOT network stack example application
2026-01-09 15:09:24,804 # All up, running the shell now
> 2026-01-09 15:09:26,331 # WiFi connected to ssid BSHS1, channel 1
heap
2026-01-09 15:10:00,299 # heap
2026-01-09 15:10:00,301 # heap: 340876 (used 97660, free 243216) [bytes]
> heap
2026-01-09 15:10:10,357 # heap
2026-01-09 15:10:10,369 # heap: 340876 (used 97660, free 243216) [bytes]
> heap
2026-01-09 15:10:20,355 # heap
2026-01-09 15:10:20,358 # heap: 340876 (used 97660, free 243216) [bytes]
> heap
2026-01-09 15:10:30,379 # heap
2026-01-09 15:10:30,381 # heap: 340876 (used 97660, free 243216) [bytes]
> heap
2026-01-09 15:15:00,492 # heap
2026-01-09 15:15:00,494 # heap: 340876 (used 97660, free 243216) [bytes]

Issues/PRs references

Instead of using the ESP CPU family to conditionally define the number of processors used by FreeRTOS, SOC_CPU_NUM_CORES can be used directly, if it exists. Otherwise it is set to 1.

Macros `xPortGetCoreID` and `xPortGetCoreID` are the same for all ESP CPU families.
RIOT generally uses only one CPU, and the second CPU, if there is one, is stalled by default. Defining portNUM_PROCESSORS here as 2 can cause problems. For example, the SYSTIMER counting units are stopped when the second CPU is disabled, which in turn leads to a memory leak because SYSTIMER is used for the esp_timer implementation to delete timers in thread context. Therefore, we define portNUM_PROCESSORS always as 1 even for ESP32x SOCs with 2 CPUs.
@github-actions github-actions bot added Platform: ESP Platform: This PR/issue effects ESP-based platforms Area: cpu Area: CPU/MCU ports labels Jan 9, 2026
@gschorcht gschorcht added Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors) CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR labels Jan 9, 2026
@gschorcht gschorcht requested a review from benpicco January 9, 2026 14:19
@riot-ci
Copy link
Copy Markdown

riot-ci commented Jan 9, 2026

Murdock results

✔️ PASSED

680abd6 cpu/esp32: use LAC timer instead of SYSTIMER for ESP32-S2

Success Failures Total Runtime
10977 0 10981 08m:56s

Artifacts

Copy link
Copy Markdown
Member

@AnnsAnns AnnsAnns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into this and the documentation around the ESP for this and it does seem like a good way to tackle this considering that RIOT OS is currently purely single core. However, I currently don't have access to any hardware to test this so I don't want to be the one to approve this 😔 Ty for the fix though!

Copy link
Copy Markdown
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for figuring this out!

@gschorcht gschorcht force-pushed the cpu/esp32/fix_esp32s3_systimer_problem branch from e857713 to 680abd6 Compare January 12, 2026 14:28
@benpicco benpicco added this pull request to the merge queue Jan 12, 2026
Merged via the queue into RIOT-OS:master with commit 117a86e Jan 12, 2026
25 checks passed
@gschorcht
Copy link
Copy Markdown
Contributor Author

@benpicco Thanks.

@leandrolanzieri leandrolanzieri added this to the Release 2026.01 milestone Jan 13, 2026
@gschorcht gschorcht deleted the cpu/esp32/fix_esp32s3_systimer_problem branch January 13, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: cpu Area: CPU/MCU ports CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Platform: ESP Platform: This PR/issue effects ESP-based platforms Type: bug The issue reports a bug / The PR fixes a bug (including spelling errors)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants