# Fix Idle Freeze: NTP Power Lock (Corrected) **Date**: 2026-03-09 ## Task Description Fix device freeze during idle caused by power management issues with NTP sync and clock refresh. This is a correction to the initial fix (commit `6cf212d`) which introduced two new problems. ## Root Cause The original freeze at the clock minute boundary was caused by the BootNtpSync background task running WiFi/NTP operations without a power lock, allowing the main loop to drop the CPU to 10 MHz during active WiFi/SPI operations. ## Initial Fix Attempt (commit 6cf212d) -- Two Problems 1. **BootNtpSync Lock**: Added `HalPowerManager::Lock` as a local variable in `taskFunc()`. However, `vTaskDelete(nullptr)` at the end of the function kills the FreeRTOS task immediately without C++ stack unwinding, so the lock destructor never ran. The lock was permanently held, preventing the device from ever entering low-power mode. 2. **Clock Refresh Power Override**: Added `setPowerSaving(false)` and `lastActivityTime` reset in the clock refresh block of `main.cpp`. This was unnecessary -- the `RenderLock` inside the render path already handles CPU frequency restoration. The explicit calls prevented the device from entering low-power mode by resetting the idle timer every minute. ## Corrected Fix ### `src/util/BootNtpSync.cpp` - Wrapped the `HalPowerManager::Lock` and all WiFi/NTP work in a block scope `{}` that ends before `vTaskDelete(nullptr)` - The lock destructor now runs when the block scope exits, properly releasing the power lock - `running = false`, `taskHandle = nullptr`, log message, and `vTaskDelete()` remain outside the scope since they don't need full CPU speed ### `src/main.cpp` - Reverted the `setPowerSaving(false)` and `lastActivityTime = millis()` additions from both clock refresh branches - Clock refresh relies on the existing `RenderLock` mechanism to handle CPU frequency during display updates ## Key Lesson FreeRTOS `vTaskDelete(nullptr)` does not unwind the C++ stack. RAII objects (like `HalPowerManager::Lock`) placed as local variables in a task function will never have their destructors called. Always use explicit block scoping or manual release before `vTaskDelete()`. ## Follow-up Items - Verify on device that low-power mode is entered after idle and that NTP sync still completes - Verify the clock still updates at minute boundaries without freezing - Consider auditing other FreeRTOS task functions in the codebase for similar RAII + `vTaskDelete` issues