Skip to content
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

Refresh diagnostics on project changes #10964

Merged
merged 9 commits into from
Oct 4, 2024

Conversation

ryzngard
Copy link
Contributor

@ryzngard ryzngard commented Oct 3, 2024

This notifies a client to refresh diagnostics when we experience project snapshot changes (except for document changed). This should help for cases where the project engine is updating but doesn't have all taghelper information yet.

@ryzngard ryzngard requested a review from a team as a code owner October 3, 2024 23:22
Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

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

I see now this is how the semantic tokens refresher works, so I'm going to approve, but I think there is an argument to be made to move both impls to the AsyncBatchingWorkQueue as its a bit more battle hardened. Could even do a common base class for both that does most of the work, and just has an abstract method to check client capabilites, and a property for which LSP method to send.

private bool? _supportsRefresh;
private bool _waitingToRefresh;
private Task _refreshTask = Task.CompletedTask;
private readonly AsyncBatchingWorkQueue _queue;
Copy link
Member

Choose a reason for hiding this comment

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

Amusingly, I had used an AsyncBatchingWorkQueue in #10140 and removed it based on feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

well I'm bringing it back! :P

var clientConnection = new StrictMock<IClientConnection>();
clientConnection
.Setup(c => c.SendNotificationAsync(Methods.WorkspaceDiagnosticRefreshName, It.IsAny<CancellationToken>()))
.Returns(Task.CompletedTask)
Copy link
Member

@DustinCampbell DustinCampbell Oct 4, 2024

Choose a reason for hiding this comment

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

Does ReturnsAsync not work here and below?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Idk tbh. What's the difference?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It looks like not. ReturnsAsync doesn't have a way to do a Task, it requires Task<T>

Copy link
Member

Choose a reason for hiding this comment

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

Ah, I think I'm remembering a Moq extension I had added in a branch to address that.

Copy link
Member

Choose a reason for hiding this comment

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

Idk tbh. What's the difference?

ReturnsAsync(...) is just convenient for returning Task<T> and ValueTask<T> in mocks.


_disposeTokenSource.Cancel();
_disposeTokenSource.Dispose();
_projectSnapshotManager.Changed -= ProjectSnapshotManager_Changed;
Copy link
Member

Choose a reason for hiding this comment

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

💭 Consider moving this above _disposeTokenSource.Cancel() so that no more work can be added before cancelling. If IProjectSnapshotManager.Updated fires while the AsyncBatchingWorkQueue is being cancelled, it might try to add work, which can result in an OperationCancelledException. Not a huge deal, but definitely unneeded.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't know adding work threw if the ABWQ cancellation happened. This is good to know , thank you!

Copy link
Member

Choose a reason for hiding this comment

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

It's a very tight race, but it's possible to land here:

_entireQueueCancellationToken.ThrowIfCancellationRequested();

return;
}

if ( _disposeTokenSource.IsCancellationRequested)
Copy link
Member

Choose a reason for hiding this comment

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

nit: extra space between ( and _. Or, if you decide to take my feedback to adjust the Dispose() method, this check isn't needed at all! 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm on a laptop today and it shows 😅

updater.CreateAndAddProject("C:/path/to/project.csproj");
});

await testAccessor.WaitForRefreshAsync();
Copy link
Member

@DustinCampbell DustinCampbell Oct 4, 2024

Choose a reason for hiding this comment

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

Since publisher is disposed above, will this ever complete?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe so. The underlying ABWQ method doesn't seem to check for cancellation, it just returns the task. That task is completed when this hits


public Task WaitForRefreshAsync()
{
return _instance._queue.WaitUntilCurrentBatchCompletesAsync();
Copy link
Member

@DustinCampbell DustinCampbell Oct 4, 2024

Choose a reason for hiding this comment

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

FWIW, there might be a hang in tests where WaitForRefreshAsync() is called after the WorkspaceDiagnosticRefresher is disposed. I'm not 100% sure though. I'm trying to "psychically debug" this. 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is it better to check disposal here and return a Task.CompletedTask?

Copy link
Member

Choose a reason for hiding this comment

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

I quick fix might be something like...

if (_instance._disposeTokenSource.IsCancellationRequested)
{
    return Task.CompletedTask;
}

Copy link
Member

Choose a reason for hiding this comment

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

Is it better to check disposal here and return a Task.CompletedTask?

LOL. I didn't refresh quickly enough to see your comment. 🤣

@ryzngard ryzngard enabled auto-merge (squash) October 4, 2024 21:50
Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

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

Love how small the class is now.

I still think its worth considering a common base class to abstract away most of the code, but I understand if it's beyond the scope of this PR, or if I'm missing some benefit of the semantic notifier being split across two classes.

@ryzngard ryzngard merged commit bfda0df into dotnet:main Oct 4, 2024
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants