Skip to content

Don't refresh dynamic files under a lock #78775

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

Conversation

jasonmalinowski
Copy link
Member

When we were getting the notification of a dynamic file being changed, we were taking the project and workspace locks before fetching the actual content. This is really expensive now that the fetching itself might not be instant.

Fundamentally this change is just switching the work to acquire the locks to get the setup info, then releasing the locks and calling the provider to get the info. I'm putting this around a batching queue so we can deduplicate lots of events at once, and also to ensure ordering so if we were to try refreshing the document more than once we don't end up in a case where we have two threads trying to put different versions of the document into the workspace and getting the ordering wrong.

Fixes #78734

It's only for dynamic files, so make that clear.
@jasonmalinowski jasonmalinowski requested a review from a team as a code owner May 30, 2025 22:11
When we were getting the notification of a dynamic file being changed,
we were taking the project and workspace locks before fetching the
actual content. This is really expensive now that the fetching itself
might not be instant.

Fundamentally this change is just switching the work to acquire the
locks to get the setup info, then releasing the locks and calling the
provider to get the info. I'm putting this around a batching queue so
we can deduplicate lots of events at once, and also to ensure ordering
so if we were to try refreshing the document more than once we don't
end up in a case where we have two threads trying to put different
versions of the document into the workspace and getting the ordering
wrong.

Fixes dotnet#78734
@jasonmalinowski jasonmalinowski force-pushed the dont-fetch-dynamic-files-under-locks branch from 1b03d1c to efbbe24 Compare May 30, 2025 22:11
return;
}
return new AsyncBatchingWorkQueue<(string, string)>(
TimeSpan.FromMilliseconds(200), // 200 chosen with absolutely no evidence whatsoever
Copy link
Member Author

Choose a reason for hiding this comment

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

This constant without proof is the same constant used for regular file change notifications (and with the same comment!)

Comment on lines -456 to -464
// We create file watching prior to pushing the file to the workspace in batching, so it's
// possible we might see a file change notification early. In this case, toss it out. Since
// all adds/removals of documents for this project happen under our lock, it's safe to do this
// check without taking the main workspace lock. We don't have to check for documents removed in
// the batch, since those have already been removed out of _documentPathsToDocumentIds.
if (_documentsAddedInBatch.Any(d => d.Id == documentId))
{
// If our project has already been removed, this is a stale notification, and we can disregard.
if (_project.HasBeenRemoved)
return;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

The old code here was covering this explicit case for something we could bail on, and asserting the document must exist otherwise. It seemed simpler just to call TryGetValue and delete the comment. Although I believe the old code was correct, it's too much work to justify it being right when we can simplify instead.

}

// Now that we've got all our basic data, let's fetch the new document outside the lock, since this could be expensive.
var fileInfo = await fileInfoProvider.GetDynamicFileInfoAsync(
Copy link
Member

Choose a reason for hiding this comment

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

Should we consider parallelizing the fetch of the dynamic files in the batch? And then maybe we could also apply all the documents in the batch at once, avoiding needing to take the workspace lock for each one?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question, that might give another speed up. @alexgav any thoughts on what might be beneficial here terms of whether there won't be some other queueing, and how much we might want to fan this out before we spam you all like crazy?

I won't block on changing this since this was already not doing parallelizing right now (so this isn't making it worse).

@jasonmalinowski jasonmalinowski self-assigned this May 31, 2025
@jasonmalinowski jasonmalinowski merged commit 0b121e1 into dotnet:main May 31, 2025
25 checks passed
@jasonmalinowski jasonmalinowski deleted the dont-fetch-dynamic-files-under-locks branch May 31, 2025 00:54
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone May 31, 2025

if (_documentPathsToDocumentIds.TryGetValue(workspaceFilePath, out var documentId))
using (await _project._gate.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
Copy link
Contributor

Choose a reason for hiding this comment

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

DisposableWaitAsync

Instead of waiting on this gate inside the loop, could we wait on it once and do a loop inside that to collect the items which need to be processed? A subsequent loop over the items to process could then do the GetDynamicFileInfoAsync/ApplyChangeToWorkspaceAsync calls

Copy link
Member Author

Choose a reason for hiding this comment

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

Waiting on #78775 (comment) -- because if we can do this in parallel generally then we'll restructure this loop entirely and do something like what you're suggesting.

If we really won't benefit from parallel processing...not sure it'll matter much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ProjectSystemProject _gate gets blocked on client RPC requests causing slow sln load
3 participants