mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-08-28 12:44:12 +00:00
fix: clean up nodeActivity in pruneStaleNodes to prevent memory leak (#553)
## Summary `nodeActivity` (an object tracking per-node packet counts for heatmap intensity) grows without bound — entries are added on every packet flash but never removed, even when stale nodes are pruned. ## Changes - **Delete `nodeActivity[key]`** alongside `nodeMarkers[key]` and `nodeData[key]` when removing stale WS-only nodes in `pruneStaleNodes()` - **Prune orphaned entries** — after the main prune loop, sweep `nodeActivity` and delete any key that has no corresponding `nodeData` entry (catches edge cases where nodes were removed by other code paths) - Both run every 60s via the existing `pruneStaleNodes` interval timer ## Testing - Added 2 regression tests in `test-frontend-helpers.js` verifying stale node cleanup and orphan removal - All 435 frontend helper tests pass, plus packet-filter (62) and aging (29) Fixes #390 --------- Co-authored-by: you <you@example.com>
This commit is contained in:
@@ -1650,6 +1650,7 @@
|
||||
}
|
||||
delete nodeMarkers[key];
|
||||
delete nodeData[key];
|
||||
delete nodeActivity[key];
|
||||
pruned = true;
|
||||
}
|
||||
} else if (marker && marker._staleDimmed) {
|
||||
@@ -1665,12 +1666,17 @@
|
||||
if (_el2) _el2.textContent = Object.keys(nodeMarkers).length;
|
||||
if (window.HopResolver) HopResolver.init(Object.values(nodeData));
|
||||
}
|
||||
// Prune orphaned nodeActivity entries (nodes removed above or never tracked)
|
||||
for (var aKey in nodeActivity) {
|
||||
if (!(aKey in nodeData)) delete nodeActivity[aKey];
|
||||
}
|
||||
}
|
||||
|
||||
// Expose for testing
|
||||
window._livePruneStaleNodes = pruneStaleNodes;
|
||||
window._liveNodeMarkers = function() { return nodeMarkers; };
|
||||
window._liveNodeData = function() { return nodeData; };
|
||||
window._liveNodeActivity = function() { return nodeActivity; };
|
||||
window._vcrFormatTime = vcrFormatTime;
|
||||
window._liveDbPacketToLive = dbPacketToLive;
|
||||
window._liveExpandToBufferEntries = expandToBufferEntries;
|
||||
|
||||
@@ -998,6 +998,56 @@ console.log('\n=== live.js: pruneStaleNodes ===');
|
||||
assert.ok(markers['apiNode'], 'API stale node should NOT be removed');
|
||||
assert.ok(data['apiNode'], 'API stale node data should NOT be removed');
|
||||
});
|
||||
|
||||
test('pruneStaleNodes cleans up nodeActivity for removed nodes', () => {
|
||||
const { ctx } = makeLiveSandbox();
|
||||
const prune = ctx.window._livePruneStaleNodes;
|
||||
const markers = ctx.window._liveNodeMarkers();
|
||||
const data = ctx.window._liveNodeData();
|
||||
const activity = ctx.window._liveNodeActivity();
|
||||
|
||||
// WS-only stale node
|
||||
markers['staleNode'] = { _glowMarker: null };
|
||||
data['staleNode'] = { public_key: 'staleNode', role: 'companion', _liveSeen: Date.now() - 48 * 3600000 };
|
||||
activity['staleNode'] = 5;
|
||||
|
||||
// Active node
|
||||
markers['activeNode'] = { setStyle: function() {}, _glowMarker: null };
|
||||
data['activeNode'] = { public_key: 'activeNode', role: 'companion', _liveSeen: Date.now() };
|
||||
activity['activeNode'] = 3;
|
||||
|
||||
prune();
|
||||
|
||||
assert.ok(!markers['staleNode'], 'stale node marker removed');
|
||||
assert.ok(!data['staleNode'], 'stale node data removed');
|
||||
assert.ok(!activity['staleNode'], 'stale node activity removed');
|
||||
assert.ok(markers['activeNode'], 'active node marker preserved');
|
||||
assert.ok(data['activeNode'], 'active node data preserved');
|
||||
assert.strictEqual(activity['activeNode'], 3, 'active node activity preserved');
|
||||
});
|
||||
|
||||
test('pruneStaleNodes removes orphaned nodeActivity entries', () => {
|
||||
const { ctx } = makeLiveSandbox();
|
||||
const prune = ctx.window._livePruneStaleNodes;
|
||||
const markers = ctx.window._liveNodeMarkers();
|
||||
const data = ctx.window._liveNodeData();
|
||||
const activity = ctx.window._liveNodeActivity();
|
||||
|
||||
// Add an active node
|
||||
markers['existingNode'] = { setStyle: function() {}, _glowMarker: null };
|
||||
data['existingNode'] = { public_key: 'existingNode', role: 'companion', _liveSeen: Date.now() };
|
||||
activity['existingNode'] = 2;
|
||||
|
||||
// Add orphaned activity (no corresponding nodeData)
|
||||
activity['ghostNode'] = 10;
|
||||
|
||||
prune();
|
||||
|
||||
assert.ok(markers['existingNode'], 'existing node preserved');
|
||||
assert.ok(data['existingNode'], 'existing node data preserved');
|
||||
assert.strictEqual(activity['existingNode'], 2, 'existing node activity preserved');
|
||||
assert.ok(!activity['ghostNode'], 'orphaned activity entry removed');
|
||||
});
|
||||
}
|
||||
|
||||
// ===== live.js: vcrFormatTime respects UTC/local setting =====
|
||||
|
||||
Reference in New Issue
Block a user