Problem
When a new client takes over an expired token in FencingManager.requestControl(), it deletes the expired token from this.tokens but does not clean up the old clientId entry from this.heartbeats.
Code path
packages/server/src/ws/fencing.ts, line 83-85:
if (now > existing.expiresAt) {
// Token expired, take over
this.tokens.delete(workspaceId);
// ← missing: this.heartbeats.delete(existing.clientId);
}
Scenario
- Tab A gets controller → heartbeat recorded
- Tab A disconnects (crash, network, etc.) →
release() never called
- Token expires (30s)
- Tab B calls
requestControl(), sees expired token, deletes it
- Tab A's heartbeat entry in
heartbeats is now orphaned — never cleaned up
Impact
Minor memory leak. Each orphaned entry is one Map entry (~tens of bytes). Not functionally impactful.
Suggested Fix
Add this.heartbeats.delete(existing.clientId) after deleting the expired token on line 85.
Problem
When a new client takes over an expired token in
FencingManager.requestControl(), it deletes the expired token fromthis.tokensbut does not clean up the oldclientIdentry fromthis.heartbeats.Code path
packages/server/src/ws/fencing.ts, line 83-85:Scenario
release()never calledrequestControl(), sees expired token, deletes itheartbeatsis now orphaned — never cleaned upImpact
Minor memory leak. Each orphaned entry is one Map entry (~tens of bytes). Not functionally impactful.
Suggested Fix
Add
this.heartbeats.delete(existing.clientId)after deleting the expired token on line 85.