diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
index 9feca8653fab0..8a25b1a252647 100644
--- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
+++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
@@ -191,6 +191,10 @@ await ComputeAndReportCurrentDiagnosticsAsync(
}
}
+ // Clear out the solution context to avoid retaining memory
+ // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1809058
+ context.ClearSolutionContext();
+
// Some implementations of the spec will re-open requests as soon as we close them, spamming the server.
// In those cases, we wait for the implementation to indicate that changes have occurred, then we close the connection
// so that the client asks us again.
diff --git a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs
index 9ad6cfcd1abca..909aed2bdb491 100644
--- a/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs
+++ b/src/Features/LanguageServer/Protocol/Handler/RequestContext.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Text;
@@ -44,22 +45,79 @@ internal readonly struct RequestContext
private readonly ILspServices _lspServices;
+ ///
+ /// Provides backing storage for the LSP workspace used by this RequestContext instance, allowing it to be cleared
+ /// on demand from all copies that may exist of this value type.
+ ///
+ ///
+ /// This field is only initialized for handlers that request solution context.
+ ///
+ private readonly StrongBox<(Workspace Workspace, Solution Solution, Document? Document)>? _lspSolution;
+
///
/// The workspace this request is for, if applicable. This will be present if is
/// present. It will be if requiresLSPSolution is false.
///
- public readonly Workspace? Workspace;
+ public Workspace? Workspace
+ {
+ get
+ {
+ if (_lspSolution is null)
+ {
+ // This request context never had a workspace instance
+ return null;
+ }
+
+ // The workspace is available unless it has been cleared by a call to ClearSolutionContext. Explicitly throw
+ // for attempts to access this property after it has been manually cleared.
+ return _lspSolution.Value.Workspace ?? throw new InvalidOperationException();
+ }
+ }
///
/// The solution state that the request should operate on, if the handler requires an LSP solution, or otherwise
///
- public readonly Solution? Solution;
+ public Solution? Solution
+ {
+ get
+ {
+ if (_lspSolution is null)
+ {
+ // This request context never had a solution instance
+ return null;
+ }
+
+ // The solution is available unless it has been cleared by a call to ClearSolutionContext. Explicitly throw
+ // for attempts to access this property after it has been manually cleared.
+ return _lspSolution.Value.Solution ?? throw new InvalidOperationException();
+ }
+ }
///
/// The document that the request is for, if applicable. This comes from the returned from the handler itself via a call to
/// .
///
- public readonly Document? Document;
+ public Document? Document
+ {
+ get
+ {
+ if (_lspSolution is null)
+ {
+ // This request context never had a solution instance
+ return null;
+ }
+
+ // The solution is available unless it has been cleared by a call to ClearSolutionContext. Explicitly throw
+ // for attempts to access this property after it has been manually cleared. Note that we can't rely on
+ // Document being null for this check, because it is not always provided as part of the solution context.
+ if (_lspSolution.Value.Workspace is null)
+ {
+ throw new InvalidOperationException();
+ }
+
+ return _lspSolution.Value.Document;
+ }
+ }
///
/// The LSP server handling the request.
@@ -97,9 +155,18 @@ public RequestContext(
ILspServices lspServices,
CancellationToken queueCancellationToken)
{
- Workspace = workspace;
- Document = document;
- Solution = solution;
+ if (workspace is not null)
+ {
+ RoslynDebug.Assert(solution is not null);
+ _lspSolution = new StrongBox<(Workspace Workspace, Solution Solution, Document? Document)>((workspace, solution, document));
+ }
+ else
+ {
+ RoslynDebug.Assert(solution is null);
+ RoslynDebug.Assert(document is null);
+ _lspSolution = null;
+ }
+
_clientCapabilities = clientCapabilities;
ServerKind = serverKind;
SupportedLanguages = supportedLanguages;
@@ -228,6 +295,14 @@ public ValueTask StopTrackingAsync(Uri uri, CancellationToken cancellationToken)
public bool IsTracking(Uri documentUri)
=> _trackedDocuments.ContainsKey(documentUri);
+ public void ClearSolutionContext()
+ {
+ if (_lspSolution is null)
+ return;
+
+ _lspSolution.Value = default;
+ }
+
///
/// Logs an informational message.
///