Skip to content

fix: prevent server process leak (#154)#155

Merged
pranaygp merged 1 commit into
masterfrom
worktree-agent-adfe79df176fc7491
May 17, 2026
Merged

fix: prevent server process leak (#154)#155
pranaygp merged 1 commit into
masterfrom
worktree-agent-adfe79df176fc7491

Conversation

@pranaygp
Copy link
Copy Markdown
Owner

Summary

  • Closes electron-nodejs (server.js) Spamming #154 (150+ electron-nodejs server.js processes, ~40-67MB each).
  • didOpenTextDocument used !clients.has(folder.uri.toString()) to avoid duplicate spawns, but inserted into clients only inside the .then() of an async Workspace.findFiles(...). Every document opened between the findFiles call and its resolution passed the guard and started a new LanguageClient for the same workspace folder. At activation, Workspace.textDocuments.forEach(didOpenTextDocument) invokes this synchronously per open document, producing one server.js process per supported document — matching the reporter's screenshot.
  • Track in-flight client creation in pendingClientFolders so concurrent calls coalesce, and clear that set on workspace folder removal so a folder closed mid-findFiles doesn't leave a stranded server.

Test plan

  • yarn build passes
  • yarn test passes (24/24)
  • Manual verification: run VS Code with a workspace containing many CSS/SCSS/LESS files (or many files in peekFromLanguages) — confirm exactly one server.js process is spawned per workspace folder, not one per document. Toggling workspace folders should add/remove processes 1:1.

Manual verification of the leak requires running VS Code for an extended period with workspaces containing many supported documents; there is no autonomous repro in the test suite.

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings May 16, 2026 20:30
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a client/server process leak in the VS Code extension by preventing multiple LanguageClient instances from being spawned concurrently for the same workspace folder during activation and rapid document opening.

Changes:

  • Adds pendingClientFolders to coalesce concurrent didOpenTextDocument calls while Workspace.findFiles(...) is in flight.
  • Cancels in-flight client creation when a workspace folder is removed to reduce the chance of spawning servers for closed folders.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread client/src/extension.ts Outdated
Comment on lines +231 to +233
client.start();
clients.set(folderKey, client);
pendingClientFolders.delete(folderKey);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d0791c6. Reordered so clients.set(folderKey, client) and pendingClientFolders.delete(folderKey) now happen before client.start() — so an onDidChangeWorkspaceFolders removal racing with this code can find the client in clients and stop it instead of leaking the server process. Wrapped start() in a try/catch: on failure we clients.delete(folderKey) (pending was already cleared) so the folder can be retried later, and we log via reporter.sendTelemetryErrorEvent.

Comment thread client/src/extension.ts Outdated
},
(err) => {
pendingClientFolders.delete(folderKey);
throw err;
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d0791c6. Replaced the rethrow in the findFiles rejection handler with reporter.sendTelemetryErrorEvent(err, { context: "client", method: "startClientForFolder" }) and an implicit return. Pending state is still cleared first, so the folder can be retried later. This avoids the unhandled promise rejection — the surrounding try/catch couldn't catch it since the .then isn't awaited.

pranaygp added a commit that referenced this pull request May 17, 2026
Builds on top of #156's pendingClientFolders race guard with three small
robustness improvements identified in the #155 review:

- Register the LanguageClient in `clients` BEFORE calling start(). If a
  workspace folder is removed mid-start, onDidChangeWorkspaceFolders can
  now find and stop the client.
- Wrap client.start() in try/catch; on failure remove from `clients` so
  the folder can be retried on the next document-open.
- Wrap Workspace.findFiles() in try/catch and log via telemetry instead
  of letting the rejection bubble up as an unhandled promise (the outer
  call site swallows it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp pranaygp force-pushed the worktree-agent-adfe79df176fc7491 branch from d0791c6 to c86bf4f Compare May 17, 2026 00:53
Builds on top of #156's pendingClientFolders race guard with three small
robustness improvements identified in the #155 review:

- Register the LanguageClient in `clients` BEFORE calling start(). If a
  workspace folder is removed mid-start, onDidChangeWorkspaceFolders can
  now find and stop the client.
- Wrap client.start() in try/catch; on failure remove from `clients` so
  the folder can be retried on the next document-open.
- Wrap Workspace.findFiles() in try/catch and log via telemetry instead
  of letting the rejection bubble up as an unhandled promise (the outer
  call site swallows it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pranaygp pranaygp force-pushed the worktree-agent-adfe79df176fc7491 branch from c86bf4f to 95dbdb0 Compare May 17, 2026 00:55
@pranaygp pranaygp merged commit 19c1887 into master May 17, 2026
3 checks passed
@pranaygp pranaygp deleted the worktree-agent-adfe79df176fc7491 branch May 17, 2026 00:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

electron-nodejs (server.js) Spamming

2 participants