debug mode added for memory testing

This commit is contained in:
cottongin 2026-01-26 13:56:36 -05:00
parent d8bee1d21f
commit a707cc6da2
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
25 changed files with 89 additions and 9 deletions

View File

@ -63,3 +63,11 @@ extends = base
build_flags = build_flags =
${base.build_flags} ${base.build_flags}
-DCROSSPOINT_VERSION=\"${crosspoint.version}\" -DCROSSPOINT_VERSION=\"${crosspoint.version}\"
[env:debug_memory]
extends = base
build_flags =
${base.build_flags}
-DCROSSPOINT_VERSION=\"${crosspoint.version}-dev\"
-DDEBUG_MEMORY=1
-DCORE_DEBUG_LEVEL=4

View File

@ -60,6 +60,7 @@ void OpdsBookBrowserActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -45,6 +45,7 @@ void DictionaryMenuActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -40,6 +40,7 @@ void DictionaryResultActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -88,6 +88,7 @@ void DictionarySearchActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -42,6 +42,7 @@ void EpubWordSelectionActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -39,6 +39,10 @@ int HomeActivity::getMenuItemCount() const {
void HomeActivity::onEnter() { void HomeActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
Serial.printf("[%lu] [HOME] [MEM] onEnter - Free: %d, Largest: %d, CoverBuffer: %s (%d bytes)\n", millis(),
ESP.getFreeHeap(), ESP.getMaxAllocHeap(), coverBufferStored ? "allocated" : "null",
coverBufferStored ? static_cast<int>(GfxRenderer::getBufferSize()) : 0);
renderingMutex = xSemaphoreCreateMutex(); renderingMutex = xSemaphoreCreateMutex();
// Check if we have a book to continue reading // Check if we have a book to continue reading
@ -153,17 +157,24 @@ void HomeActivity::onEnter() {
void HomeActivity::onExit() { void HomeActivity::onExit() {
Activity::onExit(); Activity::onExit();
Serial.printf("[%lu] [HOME] [MEM] onExit start - Free: %d, Largest: %d, CoverBuffer: %s\n", millis(),
ESP.getFreeHeap(), ESP.getMaxAllocHeap(), coverBufferStored ? "allocated" : "null");
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD // Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY); xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;
// NOTE: Do NOT free cover buffer here - keep it cached for fast re-entry // NOTE: Do NOT free cover buffer here - keep it cached for fast re-entry
// The buffer will be freed/replaced when the book changes // The buffer will be freed/replaced when the book changes
Serial.printf("[%lu] [HOME] [MEM] onExit end - Free: %d, Largest: %d\n", millis(), ESP.getFreeHeap(),
ESP.getMaxAllocHeap());
} }
bool HomeActivity::storeCoverBuffer() { bool HomeActivity::storeCoverBuffer() {
@ -202,8 +213,11 @@ bool HomeActivity::restoreCoverBuffer() {
void HomeActivity::freeCoverBuffer() { void HomeActivity::freeCoverBuffer() {
if (coverBuffer) { if (coverBuffer) {
Serial.printf("[%lu] [HOME] [MEM] Freeing cover buffer (%d bytes), heap before: %d\n", millis(),
static_cast<int>(GfxRenderer::getBufferSize()), ESP.getFreeHeap());
free(coverBuffer); free(coverBuffer);
coverBuffer = nullptr; coverBuffer = nullptr;
Serial.printf("[%lu] [HOME] [MEM] Cover buffer freed, heap after: %d\n", millis(), ESP.getFreeHeap());
} }
coverBufferStored = false; coverBufferStored = false;
} }

View File

@ -96,6 +96,7 @@ void ListViewActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -188,6 +188,7 @@ void MyLibraryActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -82,6 +82,7 @@ void CalibreWirelessActivity::onExit() {
if (networkTaskHandle) { if (networkTaskHandle) {
vTaskDelete(networkTaskHandle); vTaskDelete(networkTaskHandle);
networkTaskHandle = nullptr; networkTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
xSemaphoreGive(stateMutex); xSemaphoreGive(stateMutex);
@ -90,6 +91,7 @@ void CalibreWirelessActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -5,6 +5,7 @@
#include <GfxRenderer.h> #include <GfxRenderer.h>
#include <WiFi.h> #include <WiFi.h>
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
#include <esp_wifi.h>
#include <qrcode.h> #include <qrcode.h>
#include <cstddef> #include <cstddef>
@ -94,15 +95,18 @@ void CrossPointWebServerActivity::onEnter() {
void CrossPointWebServerActivity::onExit() { void CrossPointWebServerActivity::onExit() {
ActivityWithSubactivity::onExit(); ActivityWithSubactivity::onExit();
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap()); Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit start: %d, Largest: %d\n", millis(), ESP.getFreeHeap(),
ESP.getMaxAllocHeap());
state = WebServerActivityState::SHUTTING_DOWN; state = WebServerActivityState::SHUTTING_DOWN;
// Stop the web server first (before disconnecting WiFi) // Stop the web server first (before disconnecting WiFi)
stopWebServer(); stopWebServer();
Serial.printf("[%lu] [WEBACT] [MEM] After stopWebServer: %d bytes\n", millis(), ESP.getFreeHeap());
// Stop mDNS // Stop mDNS
MDNS.end(); MDNS.end();
Serial.printf("[%lu] [WEBACT] [MEM] After MDNS.end: %d bytes\n", millis(), ESP.getFreeHeap());
// Stop DNS server if running (AP mode) // Stop DNS server if running (AP mode)
if (dnsServer) { if (dnsServer) {
@ -110,6 +114,7 @@ void CrossPointWebServerActivity::onExit() {
dnsServer->stop(); dnsServer->stop();
delete dnsServer; delete dnsServer;
dnsServer = nullptr; dnsServer = nullptr;
Serial.printf("[%lu] [WEBACT] [MEM] After DNS server cleanup: %d bytes\n", millis(), ESP.getFreeHeap());
} }
// Brief wait for LWIP stack to flush pending packets // Brief wait for LWIP stack to flush pending packets
@ -120,16 +125,24 @@ void CrossPointWebServerActivity::onExit() {
Serial.printf("[%lu] [WEBACT] Stopping WiFi AP...\n", millis()); Serial.printf("[%lu] [WEBACT] Stopping WiFi AP...\n", millis());
WiFi.softAPdisconnect(true); WiFi.softAPdisconnect(true);
} else { } else {
Serial.printf("[%lu] [WEBACT] Disconnecting WiFi (graceful)...\n", millis()); Serial.printf("[%lu] [WEBACT] Disconnecting WiFi...\n", millis());
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame WiFi.disconnect(false); // false = don't erase credentials, just send disconnect frame
} }
delay(30); // Allow disconnect frame to be sent delay(100); // Allow disconnect frame to be sent and event callbacks to complete
Serial.printf("[%lu] [WEBACT] [MEM] After WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap());
Serial.printf("[%lu] [WEBACT] Setting WiFi mode OFF...\n", millis()); Serial.printf("[%lu] [WEBACT] Setting WiFi mode OFF...\n", millis());
WiFi.mode(WIFI_OFF); WiFi.mode(WIFI_OFF);
delay(30); // Allow WiFi hardware to power down delay(50);
Serial.printf("[%lu] [WEBACT] [MEM] After WiFi.mode(OFF): %d bytes\n", millis(), ESP.getFreeHeap());
Serial.printf("[%lu] [WEBACT] [MEM] Free heap after WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap()); // Aggressive WiFi cleanup: stop the WiFi driver to recover more memory
// WiFi is on-demand for File Transfer only, so full cleanup is safe
Serial.printf("[%lu] [WEBACT] Stopping WiFi driver (esp_wifi_stop)...\n", millis());
esp_wifi_stop();
delay(100); // Allow LWIP stack to fully release
Serial.printf("[%lu] [WEBACT] [MEM] After esp_wifi_stop: %d, Largest: %d\n", millis(), ESP.getFreeHeap(),
ESP.getMaxAllocHeap());
// Acquire mutex before deleting task // Acquire mutex before deleting task
Serial.printf("[%lu] [WEBACT] Acquiring rendering mutex before task deletion...\n", millis()); Serial.printf("[%lu] [WEBACT] Acquiring rendering mutex before task deletion...\n", millis());
@ -140,16 +153,18 @@ void CrossPointWebServerActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
Serial.printf("[%lu] [WEBACT] Display task deleted\n", millis()); // Allow idle task to free the task stack
vTaskDelay(10 / portTICK_PERIOD_MS);
Serial.printf("[%lu] [WEBACT] [MEM] After task delete + yield: %d bytes\n", millis(), ESP.getFreeHeap());
} }
// Delete the mutex // Delete the mutex
Serial.printf("[%lu] [WEBACT] Deleting mutex...\n", millis()); Serial.printf("[%lu] [WEBACT] Deleting mutex...\n", millis());
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;
Serial.printf("[%lu] [WEBACT] Mutex deleted\n", millis());
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap()); Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit end: %d, Largest: %d, MinFree: %d\n", millis(),
ESP.getFreeHeap(), ESP.getMaxAllocHeap(), ESP.getMinFreeHeap());
} }
void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) { void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode) {

View File

@ -44,6 +44,7 @@ void NetworkModeSelectionActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -82,6 +82,7 @@ void WifiSelectionActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
Serial.printf("[%lu] [WIFI] Display task deleted\n", millis()); Serial.printf("[%lu] [WIFI] Display task deleted\n", millis());
} }

View File

@ -192,6 +192,7 @@ void EpubReaderActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -88,6 +88,7 @@ void EpubReaderChapterSelectionActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -131,6 +131,7 @@ void TxtReaderActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -109,6 +109,7 @@ void XtcReaderActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -69,6 +69,7 @@ void XtcReaderChapterSelectionActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -44,6 +44,7 @@ void CalibreSettingsActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -63,6 +63,7 @@ void CategorySettingsActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -35,6 +35,7 @@ void ClearCacheActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -90,6 +90,7 @@ void OtaUpdateActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -115,6 +115,7 @@ void SettingsActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -55,6 +55,7 @@ void KeyboardEntryActivity::onExit() {
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
displayTaskHandle = nullptr; displayTaskHandle = nullptr;
vTaskDelay(10 / portTICK_PERIOD_MS); // Let idle task free stack
} }
vSemaphoreDelete(renderingMutex); vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr; renderingMutex = nullptr;

View File

@ -114,17 +114,38 @@ EpdFontFamily ui12FontFamily(&ui12RegularFont, &ui12BoldFont);
unsigned long t1 = 0; unsigned long t1 = 0;
unsigned long t2 = 0; unsigned long t2 = 0;
// Memory debugging helper - logs heap state for tracking leaks
#ifdef DEBUG_MEMORY
void logMemoryState(const char* tag, const char* context) {
Serial.printf("[%lu] [%s] [MEM] %s - Free: %d, Largest: %d, MinFree: %d\n",
millis(), tag, context,
ESP.getFreeHeap(),
ESP.getMaxAllocHeap(),
ESP.getMinFreeHeap());
}
#else
// No-op when not in debug mode
#define logMemoryState(tag, context) ((void)0)
#endif
void exitActivity() { void exitActivity() {
if (currentActivity) { if (currentActivity) {
logMemoryState("MAIN", "Before onExit");
currentActivity->onExit(); currentActivity->onExit();
logMemoryState("MAIN", "After onExit, before delete");
delete currentActivity; delete currentActivity;
currentActivity = nullptr; currentActivity = nullptr;
// Allow idle task to free FreeRTOS task stacks
delay(50);
logMemoryState("MAIN", "After delete + delay");
} }
} }
void enterNewActivity(Activity* activity) { void enterNewActivity(Activity* activity) {
logMemoryState("MAIN", "Before enterNewActivity");
currentActivity = activity; currentActivity = activity;
currentActivity->onEnter(); currentActivity->onEnter();
logMemoryState("MAIN", "After onEnter");
} }
// Verify long press on wake-up from deep sleep // Verify long press on wake-up from deep sleep