Fix: Prevent memory leak from accumulating HRM event listeners - #259
Fix: Prevent memory leak from accumulating HRM event listeners#259JaapvanEkris wants to merge 5 commits into
Conversation
This fixes issue #258 where the application crashes with a heap overflow after several hours of being stationary (no flywheel spinning). The root cause was that every time `createHrmPeripheral` was called, a new 'heartRateMeasurement' event listener was registered without removing the old one. This caused event listener accumulation over time, particularly during idle periods when the HRM watchdog timer kept resetting. The fix explicitly removes all 'heartRateMeasurement' listeners before registering a new one, preventing the accumulation of duplicate listeners that leads to the heap overflow. Fixes: #258
|
I have to go a bit deep into this to verify if this is a solution for a memory leak. Reason is that when createHrmPeripheral() runs we remove all event listeners (assuming hrmPeripheral exists) -> So at a first glance this seems redundant to me. If we try to create a new HRM and there has already been one there is code for proper cleaning up at the top of that function. EDIT: I do not see how this can be related to the watchdog, because that does not actually kill the connection, it simply resets the data to zero/undefined. This is a one shot timer. This means that the timer completes after 6 second and that is it. Never runs again. |
I'm sceptical as well (otherwise I just pushed it), but I couldn't exclude it either. Copilot's analysis: I asked Claude Opus 5 the same question, he did not find this pattern. He suspected buffers filling up: in MQTT, but also probably in BLE we keep publishing data. In BLE we even do this time driven. But if nobody actually listens, it might get queued, slowly filling up the buffer. In MQTT I haven't considered it, so that might be a thing. But MQTT is completely event-driven, so if it survives the session, it shouldn't crash the app when idle. Buffer handling is pretty deep in BLE, so I couldn't find it directly to assess if that might cause this issue. |
This is factually not true. If hrmperipheral exists we destroy it as first thing in the pipeline. Which means we cannot attach a new eventlistener to an already existing hrmPeripheral because it cannot exist at the time we call the It is clearly mixing up the two methods: createBleHrmPeripheral vs createHrmPeripheral
That I cannot exclude, this is why I asked in the issue to turn full logging on for the Peripheral so we can see what is going on when idle. But I dont see how this is possible.
Fable 5 may be? :D they are saying its the golden boy, the chosen one :D |
|
I did some AI assisted digging. I added my evaluation on whether it could be the issue. Here is a full summary of every issue found and fixed: Summary of identified bugs and fixesA —
|
Add a #stopped cancellation flag and #scanReject to allow stop() to
immediately abort an in-flight start() that is blocked on scanning or
connecting. Ensure scanner listeners are removed in stop() so no
closure can keep the HrmService reachable after destroy(). Replace
direct recursive this.start() calls in error paths with a clean
disconnect so only the existing once('disconnect') handler restarts
scanning, eliminating duplicate concurrent start() instances.
Prevents duplicate triggerAdvertising() calls if the disconnect event is emitted more than once. Aligns with FtmsPeripheral and CscPeripheral.
…troyed Add a stop() method to Pm5RowingService that clears #timer and call it from Pm5Peripheral.destroy(). Without this the timer's closure kept the entire Pm5RowingService reachable after the peripheral was replaced.
This fixes issue #258 where the application crashes with a heap overflow after several hours of being stationary (no flywheel spinning).
The root cause was that every time
createHrmPeripheralwas called, a new 'heartRateMeasurement' event listener was registered without removing the old one. This caused event listener accumulation over time, particularly during idle periods when the HRM watchdog timer kept resetting.The fix explicitly removes all 'heartRateMeasurement' listeners before registering a new one, preventing the accumulation of duplicate listeners that leads to the heap overflow.
Fixes: #258