-
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
Prevent race in LSP pull diagnostics handling that causes stale diagnostics that do not update. #54558
Prevent race in LSP pull diagnostics handling that causes stale diagnostics that do not update. #54558
Conversation
// Ensure the read to _documentIdToLastResultId to get the cached resultId and subsequent write | ||
// to _documentIdToLastResultId after calculating new diagnostics happens in a single 'transaction' | ||
// to prevent us from overwriting data in _documentIdToLastResultId with stale results. | ||
using (await _gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) |
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.
The particular issue I noticed is an issue where _documentIdToLastResultId is cleared for a document in between the read in DiagnosticsAreUnchanged and a write to it in ComputeAndReportCurrentDiagnosticsAsync for a single LSP pull request.
Consider the scenario
- A change in the editor triggers the OnDiagnosticsUpdated event to clear resultId for File.cs
- LSP sends a document pull for File.cs, we have no cached diagnostics (cleared by 1) so we begin calculation.
- A scenario like stop debugging causes OnDiagnosticsUpdated to be triggered and resultId is cleared before the calculation in 2) completes.
- The calculation in 2) completes and updates the resultId for File.cs, overwriting the clear that happened in 3) with stale results.
- The client gets the result from 4). Now the client sends the resultId from 4 for the next request, and we happily return them the stale cached results for all subsequent requests until we get a new OnDiagnosticsUpdated (like when the user types).
With my fix, OnDiagnosticsUpdated will always clear the cached resultId after or before a request has entirely completed.
- If the LSP pull request for a change comes in after the OnDiagnosticsUpdated for the same change, then it sees the cleared resultId and re-calculates.
- If the LSP pull request for a change comes in before the OnDiagnosticsUpdated for the same change, then the initial pull request will return the stale cached diagnostics, but subsequent polling by the client will soon query again at which point the stale cache will have been cleared by OnDiagnosticsUpdated and we re-calculate
…ostics that do not update.
1756d5e
to
6394826
Compare
src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
Outdated
Show resolved
Hide resolved
src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
Outdated
Show resolved
Hide resolved
else | ||
{ | ||
context.TraceInformation($"Diagnostics were changed for document: {document.FilePath}"); | ||
report = await ComputeAndReportCurrentDiagnosticsAsync(context, document, cancellationToken).ConfigureAwait(false); |
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.
having a lock around CmoputeAndReport is definitely scary. computation can take unbounded time. is it possible we can still compute outside of hte lock, but orderr things to always be consistent with teh results?
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.
yeah that make sense,, it could block us hearing about diagnostic updates for quite a while.
I wonder if I could update the _documentIdToLastResultId map before I do the computation instead of after, then release the lock and allow computation to proceed. Could potentially do that inside a try and clear out _documentIdToLastResultId if the computation throws an exception for some reason.
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.
discussed offline, Moved caching before calculation as we will only report resultIds (per document) to client if the calculation succeeds. We do not report partial document results. If there is a cancellation or exception, the client will not see a resultId for the document at all, and therefore cannot ask us for it.
src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
Show resolved
Hide resolved
src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
Outdated
Show resolved
Hide resolved
src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs
Outdated
Show resolved
Hide resolved
@vatsalyaagrawal for M2 |
I noticed this issue while investigating EnC diagnostics not updating correctly when the debug session ends (the EnC diagnostics linger after the debug session ends until a text change is made in the editor). See comment for details