-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix workspace pull diagnostics #66886
Changes from 1 commit
2d88502
b0f8f0f
8218107
55bf9d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ | |
using Microsoft.CodeAnalysis.EditAndContinue; | ||
using Microsoft.CodeAnalysis.Options; | ||
using Microsoft.VisualStudio.LanguageServer.Protocol; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; | ||
|
||
|
@@ -21,14 +22,32 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; | |
using WorkspaceDiagnosticPartialReport = SumType<WorkspaceDiagnosticReport, WorkspaceDiagnosticReportPartialResult>; | ||
|
||
[Method(Methods.WorkspaceDiagnosticName)] | ||
internal class PublicWorkspacePullDiagnosticsHandler : AbstractPullDiagnosticHandler<WorkspaceDiagnosticParams, WorkspaceDiagnosticPartialReport, WorkspaceDiagnosticReport?> | ||
internal class PublicWorkspacePullDiagnosticsHandler : AbstractPullDiagnosticHandler<WorkspaceDiagnosticParams, WorkspaceDiagnosticPartialReport, WorkspaceDiagnosticReport?>, IDisposable | ||
{ | ||
private readonly LspWorkspaceRegistrationService _workspaceRegistrationService; | ||
private readonly LspWorkspaceManager _workspaceManager; | ||
|
||
/// <summary> | ||
/// Flag that represents whether the LSP view of the world has changed. | ||
/// It is totally fine for this to somewhat over-report changes | ||
/// as it is an optimization used to delay closing workspace diagnostic requests | ||
/// until something has changed. | ||
/// </summary> | ||
private int _lspChanged = 0; | ||
|
||
public PublicWorkspacePullDiagnosticsHandler( | ||
LspWorkspaceManager workspaceManager, | ||
LspWorkspaceRegistrationService registrationService, | ||
IDiagnosticAnalyzerService analyzerService, | ||
EditAndContinueDiagnosticUpdateSource editAndContinueDiagnosticUpdateSource, | ||
IGlobalOptionService globalOptions) | ||
: base(analyzerService, editAndContinueDiagnosticUpdateSource, globalOptions) | ||
{ | ||
_workspaceRegistrationService = registrationService; | ||
_workspaceRegistrationService.LspSolutionChanged += OnLspSolutionChanged; | ||
|
||
_workspaceManager = workspaceManager; | ||
_workspaceManager.LspTextChanged += OnLspTextChanged; | ||
dibarbet marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: why do we listen for text-changed if we're alrady listening to solution changed? the former seems unnecessary. would that help simplify? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. LspSolutionChange means the backing workspace solution changed, it doesn't include changes for open documents (because we don't create a solution on LSP text changes, only when a request needing a solution comes in). |
||
} | ||
|
||
dibarbet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// <summary> | ||
|
@@ -118,4 +137,41 @@ protected override ValueTask<ImmutableArray<IDiagnosticSource>> GetOrderedDiagno | |
} | ||
}).ToImmutableArray(); | ||
} | ||
|
||
private void OnLspSolutionChanged(object? sender, WorkspaceChangeEventArgs e) | ||
{ | ||
UpdateLspChanged(); | ||
} | ||
|
||
private void OnLspTextChanged(object? sender, EventArgs e) | ||
{ | ||
UpdateLspChanged(); | ||
} | ||
|
||
private void UpdateLspChanged() | ||
{ | ||
Interlocked.Exchange(ref _lspChanged, 1); | ||
} | ||
|
||
protected override async Task WaitForChangesAsync(RequestContext context) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Consider adding another way to exit this (some sort of shutdown cancellation token) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤦 ... yes this should absolutely use the cancellation token passed in by the queue. that'll take care of either request cancellation / shutdown. fixed! |
||
{ | ||
// Spin waiting until our LSP change flag has been set. When the flag is set (meaning LSP has changed), | ||
// we reset the flag to false and exit out of the loop allowing the request to close. | ||
// The client will automatically trigger a new request as soon as we close it, bringing us up to date on diagnostics. | ||
while (Interlocked.CompareExchange(ref _lspChanged, value: 0, comparand: 1) == 0) | ||
{ | ||
// There have been no changes between now and when the last request finished - we will hold the connection open while we poll for changes. | ||
await Task.Delay(TimeSpan.FromMilliseconds(100)).ConfigureAwait(false); | ||
} | ||
|
||
context.TraceInformation("Closing workspace/diagnostics request"); | ||
// We've hit a change, so we close the current request to allow the client to open a new one. | ||
return; | ||
dibarbet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
public void Dispose() | ||
{ | ||
_workspaceManager.LspTextChanged -= OnLspTextChanged; | ||
_workspaceRegistrationService.LspSolutionChanged -= OnLspSolutionChanged; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you please put Dispose right after constructor? i find it much easier to reason about that way. :) |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -87,6 +87,8 @@ public LspWorkspaceManager( | |
_lspWorkspaceRegistrationService = lspWorkspaceRegistrationService; | ||
} | ||
|
||
public EventHandler<EventArgs>? LspTextChanged; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doc. |
||
|
||
#region Implementation of IDocumentChangeTracker | ||
|
||
/// <summary> | ||
|
@@ -102,6 +104,8 @@ public void StartTracking(Uri uri, SourceText documentText) | |
|
||
// If LSP changed, we need to compare against the workspace again to get the updated solution. | ||
_cachedLspSolutions.Clear(); | ||
|
||
LspTextChanged?.Invoke(this, EventArgs.Empty); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -120,6 +124,8 @@ public void StopTracking(Uri uri) | |
|
||
// Also remove it from our loose files workspace if it is still there. | ||
_lspMiscellaneousFilesWorkspace.TryRemoveMiscellaneousDocument(uri); | ||
|
||
LspTextChanged?.Invoke(this, EventArgs.Empty); | ||
} | ||
|
||
/// <summary> | ||
|
@@ -135,6 +141,8 @@ public void UpdateTrackedDocument(Uri uri, SourceText newSourceText) | |
|
||
// If LSP changed, we need to compare against the workspace again to get the updated solution. | ||
_cachedLspSolutions.Clear(); | ||
|
||
LspTextChanged?.Invoke(this, EventArgs.Empty); | ||
} | ||
|
||
public ImmutableDictionary<Uri, SourceText> GetTrackedLspText() => _trackedDocuments; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious. why only do this in the public workspace pull side? is the non-public side not affected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, the client polls in the other version.