-
-
Notifications
You must be signed in to change notification settings - Fork 8.2k
esp32: Heap fragmentation and mbedtls/lwip. #8940
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
For comparison on IDF 4.2, the heap total is 246288, the largest contiguous free block is 113804, and it allocates that full amount for the MicroPython heap. After that, the remaining contiguous blocks are 76776, 15036, 4456. After connecting to wifi, the contiguous blocks are 35128, 15036. At the wrap_socket call, it's 33300, 15036 (compare to 22528, 13312, 1600 for IDF 4.4). |
I don't understand this conclusion: to get BLE, WiFi and lwIP working needs 19k+45.5k+7k = ~ 72kiB free in the IDF heap. For that you could leave blocks 1&2, or better 2&3, for the IDF. Then uPy could use the other two blocks for its heap. Or could leave block 4 (112208 bytes) for the IDF and uPy could use blocks 1, 2 and 3 for its heap. Wouldn't that work and allow everything to run at the same time (with one ssl socket)? |
The bigger picture is that WiFi+BLE+SSL sockets requires a lot of resources, pushing and embedded device to its limit. And the IDF itself is rather hungry when it comes to RAM usage, because the WiFi and BLE subsystems run on the same SoC (in contrast to stm32 where the WiFi and BLE are off chip). Nevertheless, it's still possible to do all these things at once, albeit with only a little RAM to spare for the actual application (PSRAM would make things a lot better). I think it'd be great if we could be as flexible as possible with RAM usage so that the user can do WiFi+BLE+SSL (without PSRAM) if they need. And if they only need two out of those three items then memory is not unused/wasted/reserved for the other part that they don't need. |
That's 73kiB total for the IDF. This means you can only have a single (non-secure) socket connected, regardless of SSL.
So currently we give 1&2&3, but 1 is only 4kiB, so it's pretty similar. Split heap is just enabling that 4kiB from 1 to go to uPy.
I hadn't considered this because I figured reducing the size of the uPy heap wasn't a good option. I guess you could also partially allocate ~20-25kiB of block 4 to uPy as well, which would come pretty close to the current uPy heap size. (Although when it comes to being able to allocate the SSL buffers successfully in a potentially fragmented uPy heap, I think it's better than uPy has a single large contiguous heap? Although with the proposed SSLContext we can avoid this by forcing early alloc) |
I think we need to, to make things "just work" by default. Maybe we can optionally increase the size of the uPy heap by adding regions at runtime. This could be done automatically (eg when GC heap runs out) or explicitly by the user, eg So we'd need:
|
Not sure if this could be of any help or maybe you already have looked into it, but just in case: or CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA Although not sure how much heap would it save, or if it would be enough to solve the ENOMEM errors... 🤔 Also with |
Any updates on this? |
If you're currently blocked by out of memory issues when using SSL, I have a (not thoroughly tested) change in my tree, quickly committed here, that implements allocating mbedtls buffers from the MicroPython heap. Feel free to pick it for your local development build. It did solve my issues on IDF4.4, unblocking me for now. I believe Jim mentioned in #8915 he was testing with a similar change. |
@DvdGiessen @AmirHmZz Yes this is exactly the approach I used, however note that there will be some calls from mbedtls that are not on the MicroPython task, in which case accessing the MicroPython heap will not work. The solution I had for this was just to put an extra layer between mbedtls and m_tracked_calloc to check the thread state:
then in main()
|
I think we can't to count with the
Excellent! That will be great. Just bringing to my scenario as an example where I need 6 persistent |
Is it useful to have the mbedTLS wrapper allocators try to allocate from the IDF heap first, and only fall back to Python heap if the IDF heap allocation fails? I'm thinking that although there are going to be runtime configurations where the IDF heap is full and the Python heap may be empty, there are equally likely to be some where IDF heap is relatively empty (i.e. if Bluetooth is not in use, or perhaps if PSRAM is enabled). Means the free path gets fiddly with checking what region a pointer lands on, of course. |
Maybe! My thought was that the benefit of stealing back that "unused" IDF heap to give to MicroPython would be overall better. (But yes like you say, tracking this for free will be interesting) |
Oh, if that's on the agenda then it makes total sense! (Is the end result going to be replacing the IDF heap component with one that wraps all C allocations from the MP heap? 😁) |
Looks like ESP-IDF v5 (beta release in August 2022) is switching to mbedTLS v3.x per https://github.com/espressif/mbedtls/wiki#mbed-tls-support-in-esp-idf May affect the need (or approach) for this issue? Here is the effort to migrate to mbedTLS v3.x #8988 which is on hold until after 1.20 release |
When I was using mpy 1.13 I had two different builds, one with BLE and one without. I normally don't use BLE and the savings from removing it are just too big to ignore. The BLE build used:
which was picked up in main to keep a specific amt of memory for IDF. IMHO it would be better to have a way to adjust the heap in boot.py as suggested in #6785 than to create complicated memory allocation schemes that only lead to very difficult to troubleshoot bugs later... |
Hi, Just wanted to add that the issues with running out of memory with |
any update? |
looks like circuitpython 8.0 candidate includes update to 4.4. Curious how they dealt with the fragmentation:
There may be something in this pull request? adafruit/esp-idf#11 |
(belated comment)
I am not sure if we (CircuitPython) are doing anything special. We do have a limited BLE implementation, but almost no one uses it. Since nimble doesn't do dynamic service creation out of the box, it doesn't match our nRF-inspired API. We are considering how to improve nimble or get around that, but haven't done anything yet. So we may just not be running into the wifi+BLE storage limits right now. I am not that familiar with the details of our Espressif wifi code. @tannewt Do you have any comment here? |
Are there any new developments? |
@Mopele yes. v1.21 and the recent v1.22 included several changes related to this. See https://github.com/orgs/micropython/discussions/12316 |
Require explicit socket port reuse
Uh oh!
There was an error while loading. Please reload this page.
This is a meta-issue to track esp32 heap fragmentation issues and analysis of possible solutions and workarounds.
(See original analysis in #5543 and other reports/related work in #7038, #8628, #8662, #8251, #5355, #7061, #5219, #5808, #7214)
The high-level issue is that on, for example, a pico-d4 with IDF v4.4 the RAM layout at startup is:
MicroPython's current logic is that it calculates the total (8-bit capable) IDF heap size (244072), the largest contiguous free block (110592), and then tries to allocate
min(244072 / 2, 110592) = 110592
. (get_largest_free_block
returns 110592 when that block is actually 112208.. i.e. round to 4kiB -- looking at the implementation of the IDF allocator, it does power-of-two allocations)This leaves four fragments for the IDF heap 4452 + 69256 + 13440 + 1612 bytes.
From this, the IDF allocates for:
For example, fetching a http resource. After wifi is connected, the largest available IDF heap alloc is 25600. This drops to 15616 while the socket is open (9.75kiB total) and returns to 25600 after close.
Fetching an https resource, at the point wrap_socket is called, the available IDF blocks are (22528, 13312, 1600) (total 37kiB). There's a further ~7kiB of lwip mallocs, plus the 35kiB of mbedtls. The request subsequently fails due to an OOM.
mbedtls calls malloc a lot -- the sequence of allocations up to the first free is (16717,4429,220,128,2240,16,344,1435,32,32,172,260,4,16,16,16,16,16,16,16,344,1306,32,32,32,32,172,260,4,16,16,344,1380,32,32,32,172,516,4,16,4,4,32).
This shows clearly why it's impossible to do an SSL request on IDF 4.4.
I implemented #8526 for ESP32 and used
mbedtls_platform_set_calloc_free
to intercept mbedtls allocs. The implementation was to reserve a contiguous 64kiB for IDF (for lwip, wifi), then use all remaining IDF blocks for split heaps. This successfully does an https request. But there can only be one concurrent request, and BLE cannot be enabled. So the problem is, in order to keep everything else working (BLE, wifi, lwip) you can only really afford to take the first block anyway, so the benefit of split heap is marginal here.The text was updated successfully, but these errors were encountered: