-
-
Notifications
You must be signed in to change notification settings - Fork 172
NimBLE_Scan_Continuous.ino possible heap memory leak? #591
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
Sorry, I'm not very experienced with BLE and have problems of my own, but here's what I've noticed: It looks like it always decreases by 72 bytes every 16~18 seconds or so whether or not a new advertised device is found. It's a pretty simple example program in my opinion. The only call I'm not familiar with is; NimBLEDevice::setScanDuplicateCacheSize(200); I've never used this function, but it seems like it would be used to save the previously discovered mac addresses to compare against newly discovered mac addresses. Looking at the code backs that up. I see nothing obvious (which is pretty much all I would notice given my programming skills), but I'm guessing it has to do with the duplicate entries, which are not being reported due to being duplicates, are somehow still taking up heap space. Sorry I can't help more than that. If you're up to it, here's the part of the code where the function is located. Maybe you can track it down if you really need: Starts on line 503. https://github.com/h2zero/NimBLE-Arduino/blob/release/1.4/src/NimBLEDevice.cpp Before I go, I just noticed the comment above that function... here's the whole block from the above link:
"* @param [in] cacheSize The number of advertisements filtered before the cache is reset.\n" If you try a smaller cache size or watch the heap longer, you might see the cache get dumped and restart. Then the heap would suddenly get smaller before looking like it is leaking again. At least that's the impression I get from that comment, and it backs up my initial guess. If it is recording duplicate advertisements to the cache instead of deleting duplicates, that would explain this behavior. Maybe a better method would involve deleting duplicates instead of just adding them to the cache if this is indeed the case. Sorry I can't help more than this, but it may be the intended functionality. We'll have to wait for word from h2zero. |
Thanks, I can't say i've noticed but I will test and get back |
I've also noticed something similar. I recently migrated 5 devices to use NimBLE instead of the "native" Arduino BLE and all devices are exhibiting the same behaviour. I have put together a sample of the heap size over the last ~12 hours here. It's worth noting that I know it's related to the NimBLE refactor because it's the only thing that was changed between these releases. The relevant code is: void connectToThermometer()
{
NimBLEDevice::init("");
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
pBLEScan = NimBLEDevice::getScan();
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), false);
pBLEScan->setInterval(97);
pBLEScan->setWindow(37);
pBLEScan->setMaxResults(0);
pBLEScan->setActiveScan(true);
startBLE();
}
void startBLE()
{
pBLEScan->start(0, nullptr);
}
bool isBLEStopped()
{
return pBLEScan->isScanning() == false;
} inside the loop: ...
if (isBLEStopped())
{
Serial.println('scanning failed, restarting ble');
startBLE();
delay(2000);
}
... |
looking into this a bit further, it seems that I've added logs into the file: NIMBLE_LOGI(LOG_TAG, "New advertiser: %s", advertisedAddress.toString().c_str());
+ NIMBLE_LOGI(LOG_TAG, "Number of vectors: %d", pScan->m_scanResults.m_advertisedDevicesVector.size());
} else if (advertisedDevice != nullptr) {
NIMBLE_LOGI(LOG_TAG, "Updated advertiser: %s", advertisedAddress.toString().c_str()); and i can see that over a while, this number slowly increases (using the code shown above). I added the following: diff --git a/NimBLE-Arduino-1.4.1/src/NimBLEScan.cpp b/NimBLE-Arduino-1.4.1/src/NimBLEScan.cpp
index d1c4879..bce89a1 100644
--- a/NimBLE-Arduino-1.4.1/src/NimBLEScan.cpp
+++ b/NimBLE-Arduino-1.4.1/src/NimBLEScan.cpp
@@ -23,7 +23,7 @@
#include <climits>
static const char* LOG_TAG = "NimBLEScan";
-
+static const size_t maxAdvertisedDevices = 2;
/**
* @brief Scan constuctor.
@@ -110,6 +110,11 @@ NimBLEScan::~NimBLEScan() {
return 0;
}
+ if (pScan->m_scanResults.m_advertisedDevicesVector.size() >= maxAdvertisedDevices) {
+ delete pScan->m_scanResults.m_advertisedDevicesVector.front();
+ pScan->m_scanResults.m_advertisedDevicesVector.erase(pScan->m_scanResults.m_advertisedDevicesVector.begin());
+ }
+
advertisedDevice = new NimBLEAdvertisedDevice();
advertisedDevice->setAddress(advertisedAddress);
advertisedDevice->setAdvType(event_type, isLegacyAdv); to test the theory that this was the issue and it did indeed make the memory to stabilise, so everything seems to be pointing to this vector being the problem (and old entries not being deleted + erased correctly). The patch above is obviously not a permanent fix but should help to implement a long term solution. |
I have looked into this and the cause is due to active scanning and how it is handled. When a device is advertising with a scan response available the device is added to the devices vector and awaits the scan response data before calling the callback and is not deleted from the vector until the scan response is received. The problem occurs when the scan response is never received, which happens quite regularly. To work around this (for now) I suggest stopping and restarting the scan occasionally to clear out the vector, or do not enable active scanning. I will contemplate options for the future to resolve this internally. |
Thanks @h2zero for giving more clarity here. I need to maintain active scanning for various reasons, so would the temporary solution be to simply call In the documentation, the final pBLEScan->start() object states that when false, it clears the previous scan... is that not correct or does it simply not reuse the previous scan results? |
I would suggest using a timer that triggers say every 15 or 20 mins and in the callback stop the scan with
The documentation is correct, when the I would also add that you will need to delay for a couple milliseconds after stopping the scan before restarting for the stack to cleanup. |
That was the conclusion I came to as well. Would an option to clear out items from the vector that haven't advertised in N seconds?
The reason I migrated to NimBLE is because i found that starting and stopping BLE on the ESP32 eventually causes a OWDT, restart (and using HTTP + BLE at the same time wasn't possible without using NimBLE so had to stop BLE before making a HTTP request). I'd half narrowed it down to the continuous starting/stopping of BLE (although I was doing this every 1 minute instead of 15/20 as suggested in this thread). For me, I can disable active scanning for now, see how the devices behave and report back. Obviously this doesn't help @nravanelli. |
I'm thinking about implementing a scan response timer that will invoke the advertised device callback and then remove it from the vector when max devices is 0 if the scan response doesn't come within 500ms. |
just to follow up, as expected disabling active scanning does indeed behave normally and there's no memory leak. |
@scripter-co @nravanelli I have created a branch that should resolve this here: https://github.com/h2zero/NimBLE-Arduino/tree/sr-timer Please try it out and let me know, thanks! |
Ill give it a go now, thanks! |
@h2zero I have been running an ESP32 with your new fixes to active scan for a little over 40 minutes and HEAP use has stayed constant at So I'd say this is resolved! |
looks good. question though, if |
Indeed, the error persists when ‘max_results !=0’. In my actual code I iterate through the scan results after the scan times out and I can see the heap leak persists. When used within the example “scan continuous” example it is fixed |
I was making the assumption that if max_results is not 0 that the scan would stop and be restarted at some point, so not continuous. When restarting the scan the results are typically cleared at that time. If the scan is not restarted then yes the memory would still be occupied, in that case a call to clearResults should be done. |
i'm struggling to find it, could you point out the line where |
Not sure I follow? The process is that if scanning continuously with max results = 0 that after the callback is called the device is deleted from the vector. If max results is not 0 devices will be added to the vector until the scan stops and only deleted when the scan is started again, with the isContinue flag set to false. |
@h2zero sorry for the delayed response. i'd misunderstood so all good. did this fix make it into master in the end? |
I decided against this approach, instead I have added a parameter to scan::start that when true will stop the current scan and clear the vector before starting again. |
I also want to report a problem. Repeated calls to BLEDevice::init() BLEDevice::deinit() will also cause a small amount of memory leak. |
@lewisxhe Could you share some example code to reproduce this? |
#include <Esp.h>
#include "NimBLEDevice.h"
bool running = false;
uint32_t loopCount = 0;
uint32_t lastFreeHeap = 0;
void hw_print_mem_info()
{
Serial.printf("------------------------------------------\n");
Serial.printf(" Loop Count:%u\n", loopCount++);
uint32_t totalHeap = ESP.getHeapSize();
uint32_t freeHeap = ESP.getFreeHeap();
Serial.printf(" Total Size : %u B ( %.1f KB)\n", totalHeap, totalHeap / 1024.0);
Serial.printf(" Free Bytes : %u B ( %.1f KB)\n", freeHeap, freeHeap / 1024.0);
Serial.printf(" Minimum Free Bytes: %u B ( %.1f KB)\n", ESP.getMinFreeHeap(), ESP.getMinFreeHeap() / 1024.0);
Serial.printf(" Largest Free Block: %u B ( %.1f KB)\n", ESP.getMaxAllocHeap(), ESP.getMaxAllocHeap() / 1024.0);
if (lastFreeHeap != freeHeap) {
if (lastFreeHeap > freeHeap) {
Serial.printf(" lastFreeHeap:%u Now:%u less %u B\n", lastFreeHeap, freeHeap, lastFreeHeap - freeHeap);
}
lastFreeHeap = freeHeap;
}
Serial.printf("------------------------------------------\n");
}
void setup()
{
Serial.begin(115200);
hw_print_mem_info();
}
void loop()
{
if (running) {
NimBLEDevice::deinit(true);
} else {
NimBLEDevice::init("NimBLE");
hw_print_mem_info();
}
running ^= 1;
delay(1000);
}
This is my test code. Every time init and deinit are called, 48B of memory will be reduced. I don't think this is a NIMBLE problem. I suspect it is an underlying problem of ESP32-CORE. Since you asked, I will reply to you and let you know. |
I've made an improved version that is safer as it runs in the host task #947. |
I have been noticing a potential memory leak in the continuous scan example. I have only added the following to the end of
void loop()
:And I see heap memory use reduce continuously:
Is there something that is not being cleared properly in the example?
The text was updated successfully, but these errors were encountered: