Skip to content

Fix language server retaining pre-edit program and its checkers after program clone - #64466

Open
Nathnael Wondisha (ghost2023) wants to merge 2 commits into
microsoft:mainfrom
ghost2023:lsp-mem-fix
Open

Nathnael Wondisha (ghost2023) wants to merge 2 commits into
microsoft:mainfrom
ghost2023:lsp-mem-fix

Conversation

@ghost2023

@ghost2023 Nathnael Wondisha (ghost2023) commented Sep 26, 2026 •

Copy link
Copy Markdown

Fixes #64465

I was attempting to find a decrease the memory usage of the lsp and found a unneccessary clone of the program.

Problem

fileLoader.addProjectReferenceTasks stores the full ProgramOptions on projectReferenceFileMapper. ReuseProgram shares that mapper with every cloned program through processedFiles. In the language server, opts.CreateCheckerPool and opts.CreateModuleResolver are closures that capture the *project.Project, which creates this chain of references:

new Program → projectReferenceFileMapper → opts.CreateCheckerPool
  → old *Project → old Program → old checkerPool → old Checkers

As a result, after the first edit the program from before the edit, its checker pool and its checkers stay reachable for as long as any clone of it is alive. Its pool has already been Discard()ed, so those checkers are never idle-cleaned either. The issue has the full analysis.

Fix

The mapper only reads Config, Host and UseSourceOfProjectReference, and never calls the factory closures. This change clears CreateCheckerPool and CreateModuleResolver from the copy of the options the mapper keeps. Behavior is otherwise unchanged.

Results

I measured this with a scripted LSP client on a Next.js app of about 500 files with heavy use of type inference with Orpc. The client opens 5 files, requests diagnostics, hover and completion, and makes one edit. Heap is inuse_space after GC. Numbers are the average of 3 runs.

before after
Go heap, active ~541 MB ~464 MB (−14%)
Go heap, idle (after checker idle timeout) ~471 MB ~388 MB (−18%)
RSS, idle ~718 MB ~600 MB (−16%)

Testing

Added TestSnapshot/cloned program does not retain the program it was cloned from. It edits a file so the program is cloned, then asserts the pre-edit program is garbage-collected. The test fails without this change and passes with it.

I ran the full npx hereby test, lint and check:format.
I have use AI for this PR. I fully reviewed the code and and understood it enough to be confident in it.

Copilot AI balanced review requested due to automatic review settings September 26, 2026 12:28
@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Sep 26, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

The targeted fix matches mapper usage and is covered by an appropriate regression test.

Review effort: Balanced
Findings: None

What changed in this PR

Fixes language-server memory retention after cloned program updates by removing unused closure references from the shared project-reference mapper.

Changes:

  • Clears checker-pool and module-resolver factories from retained mapper options.
  • Adds a GC-based regression test for cloned programs.
File Description
tsc/​internal/​compiler/​fileloader.go Prevents shared mapper options from retaining old projects.
tsc/​internal/​project/​snapshot_test.go Verifies pre-edit programs become collectible.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

@ghost2023

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree

@jakebailey

Copy link
Copy Markdown
Member

I think there's actually another one of these in ReuseProgram via processedFiles.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

Language server retains the pre-edit program and its checkers for the rest of the session after the first edit

3 participants