perf(diagnostics): share analyzer results across document requests - #404
Open
alsi-lawr wants to merge 2 commits into
Open
perf(diagnostics): share analyzer results across document requests#404alsi-lawr wants to merge 2 commits into
alsi-lawr wants to merge 2 commits into
Conversation
razzmatazz
reviewed
Aug 7, 2026
|
|
||
| type private SolutionAnalysisCache = ConcurrentDictionary<ProjectId, Lazy<Task<ImmutableArray<Diagnostic>>>> | ||
|
|
||
| let private solutionAnalysisCaches = |
Owner
There was a problem hiding this comment.
not a fan of this global cache. can we add this to wf structure? unsure how state transfers/caching/locking would work though
Contributor
Author
There was a problem hiding this comment.
not a fan of this global cache. can we add this to wf structure? unsure how state transfers/caching/locking would work though
What exactly do you mean by "add this to wf"? Do you mean:
LspWorkspaceFolderholds the cache, which is then plugged into the WorkspaceFolder life-cycle by being created at solution load, and dropped in teardown? This would be a big improvement, I agree.- The cache itself is objectionable and we should just thread the record data through
LspWorkspaceFolderUpdateFn? This would gut the PR, because it'll no longer cache the in-flightTaskproperly. The UpdateFns apply only after the handlers are evaluated, so the burst of diagnostics will always just see an empty set of diagnostics and re-request them all again.
Contributor
Author
There was a problem hiding this comment.
Went ahead with option 1 there since Option 2 should just close the PR/Issue as rejected
Move the shared analyzer cache from a module-level global into LspWorkspaceFolder so it is reachable from ServerState and released by workspaceFolderTeardown rather than when the GC clears the weak keys. Locking, invalidation, and retention are unchanged: the ConditionalWeakTable moves intact into AnalyzerDiagnosticsCache, still keyed by immutable Solution snapshot.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #403.
Diagnosis
Document diagnostics currently create a new
CompilationWithAnalyzersand runGetAllDiagnosticsAsyncfor every requested document. The result covers the whole project, but each request keeps only diagnostics for its own syntax tree. Pulling several documents from one unchanged solution snapshot therefore repeats the same project-wide analyzer work.Change
Cache one lazy analyzer task per project and immutable Roslyn
Solutionsnapshot. Document and compilation diagnostics share that result. A new solution snapshot naturally gets a new cache, and the weak solution key allows old snapshots to be collected.Request cancellation only cancels that request's wait. It does not cancel analyzer work already shared with another document request.
The production change is 43 added lines and 18 removed lines in
Roslyn/Analyzers.fs.User impact
Diagnostic payloads and update behavior do not change. The first analyzer request for a solution snapshot still performs the full analysis. Later document requests for the same snapshot reuse it. Editing any document creates a new snapshot, so the next request analyzes the updated project before reuse begins again.
A real-LSP four-file evaluation covered same-file edits that clear and add IDE diagnostics, saves, unrelated document pulls, cross-file edits that add and clear
CS0103, workspace pulls, and push diagnostics. Normalized diagnostic payloads matched the baseline in all three fresh sessions.Benchmark
tests/benchmarks/AnalyzerDiagnostics.fsxstarts the real LSP, enables analyzers, warms one document, then pulls diagnostics for 12 other documents in the same project snapshot. It prints the actual request times and diagnostic counts.Upstream main (
e2efc47), representative run:This branch (
c2002e0), representative run:Across three fresh server processes, the median wall time fell from 572 ms to 27 ms. Median server CPU time fell from 2,150 ms to 30 ms. Every sample returned the same 72 diagnostics.
Verification
The analyzer cache regression test also covers concurrent document requests, request cancellation isolation, completed-result reuse, and invalidation after a document edit.