Skip to content

FencingManager heartbeat orphaned on expired token takeover #14

@pallyoung

Description

@pallyoung

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

  1. Tab A gets controller → heartbeat recorded
  2. Tab A disconnects (crash, network, etc.) → release() never called
  3. Token expires (30s)
  4. Tab B calls requestControl(), sees expired token, deletes it
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions