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

Fix lock contention in ProjectRootElementCache.Get #6680

Merged

Conversation

AR-May
Copy link
Member

@AR-May AR-May commented Jul 15, 2021

Fixes #3039

Context

There is significant amount of lock contentions in ProjectRootElementCache.Get method.
(For OrchardCore project, for example, the total time spent waiting to take a lock ~1sec.)
Reason: the code is using a single lock to control to access the cache, and also read file into XmlDocument, when it does not exist. The later one can be slow on a slow disk.

Changes Made

The ProjectRootElementCache.Get function is rewritten to exclude IO operations from the locked sections.
For this goal we also had to separate logic of loading from logic of adding the element to the cache.

Testing

Unit tests + manual testing.

src/Build/Evaluation/ProjectRootElementCache.cs Outdated Show resolved Hide resolved
src/Build/Evaluation/ProjectRootElementCache.cs Outdated Show resolved Hide resolved
@AR-May AR-May marked this pull request as draft July 16, 2021 11:00
@AR-May
Copy link
Member Author

AR-May commented Jul 16, 2021

Experimental insertion in VS showed problems with these changes. Converting this PR to draft while I investigate and fix.

@AR-May AR-May force-pushed the lock-contention-in-ProjectRootElementCache-3 branch from 57cfc33 to a6db102 Compare July 20, 2021 16:26
@AR-May AR-May marked this pull request as ready for review July 21, 2021 09:27
@AR-May
Copy link
Member Author

AR-May commented Jul 21, 2021

Ready for review now, the errors does not seem to be related to the changes after investigation.

@@ -63,8 +63,11 @@ private ProjectRootElement GetFromOrAddToCache(string projectFile, OpenProjectRo
ErrorUtilities.VerifyThrowInternalNull(rootElement, "projectRootElement");
ErrorUtilities.VerifyThrow(rootElement.FullPath.Equals(key, StringComparison.OrdinalIgnoreCase),
"Got project back with incorrect path");

AddEntry(rootElement);
Copy link
Member

Choose a reason for hiding this comment

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

May I ask you to explain this statement? We're inside the GetOrAdd callback so rootElement will be added to the cache with the key of projectFile. Why are we calling another add?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, I removed AddEntry from the end of all the open functions in order to divide downloading and adding to the cache code. So, now everywhere else where I used open functions, I now need to do AddEntry exactly afterwards, so that behavior is exactly the same as before.
Here, in case of SimpleProjectRootElementCache, AddEntry does a bit more than just adding to _cache (it raises an event additionally).

Copy link
Member

Choose a reason for hiding this comment

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

Thank you, yes, it does what it was doing before. It doesn't look right, though, to be adding inside the GetOrAdd callback. It means that the item will be added twice - by AddEntry and then attempted another add by GetOrAdd after the callback returns. It looks as though this should be a TryGetValue instead of GetOrAdd.

Just a perf nit, really, and OK to ignore.

src/Build/Evaluation/ProjectRootElementCache.cs Outdated Show resolved Hide resolved
src/Build/Evaluation/ProjectRootElementCache.cs Outdated Show resolved Hide resolved
src/Build/Evaluation/ProjectRootElementCache.cs Outdated Show resolved Hide resolved
src/Build/Evaluation/ProjectRootElementCache.cs Outdated Show resolved Hide resolved
src/Build/Evaluation/ProjectRootElementCache.cs Outdated Show resolved Hide resolved
@Forgind Forgind added the merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. label Jul 23, 2021
@ladipro ladipro merged commit 65e44d9 into dotnet:main Jul 26, 2021
Forgind pushed a commit that referenced this pull request Dec 23, 2021
Fixes possible race condition in ProjectRootElementCache.Get supposedly introduced in #6680.

Context
Passing a load function that uses ProjectRootElementCache.Get to ProjectRootElementCache.Get function could lead to failure (to verify a path in the loaded ProjectRootElement), due to a race condition. We eliminate calling ProjectRootElementCache.Get inside the load function, eliminating an unnecessary double entry to ProjectRootElementCache.Get function.

Changes Made
Remove double entry to ProjectRootElementCache.Get function that could cause race condition.
Adjust path verification in ProjectRootElementCache and SimpleProjectRootElementCache to be more safe and to have more information.
Testing
Unit tests + manual testing

Notes
The stack trace of failure:

> ##[error]C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\CommonExtensions\Microsoft\NuGet\NuGet.RestoreEx.targets(19,5): Error : MSB0001: Internal MSBuild Error: Got project back with incorrect path 
> at Microsoft.Build.Shared.ErrorUtilities.ThrowInternalError(String message, Exception innerException, Object[] args) 
> at Microsoft.Build.Evaluation.ProjectRootElementCache.Get(String projectFile, OpenProjectRootElement openProjectRootElement, Boolean isExplicitlyLoaded, Nullable1 preserveFormatting) 
> at Microsoft.Build.Evaluation.Evaluator4.ExpandAndLoadImportsFromUnescapedImportExpression(String directoryOfImportingFile, ProjectImportElement importElement, String unescapedExpression, Boolean throwOnFileNotExistsError, List1& imports) 
> at Microsoft.Build.Evaluation.Evaluator4.ExpandAndLoadImportsFromUnescapedImportExpressionConditioned(String directoryOfImportingFile, ProjectImportElement importElement, List1& projects, SdkResult& sdkResult, Boolean throwOnFileNotExistsError) 
> at Microsoft.Build.Evaluation.Evaluator4.ExpandAndLoadImports(String directoryOfImportingFile, ProjectImportElement importElement, SdkResult& sdkResult) 
> at Microsoft.Build.Evaluation.Evaluator4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement) 
> at Microsoft.Build.Evaluation.Evaluator4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport) 
> at Microsoft.Build.Evaluation.Evaluator4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement) 
> at Microsoft.Build.Evaluation.Evaluator4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport) 
> at Microsoft.Build.Evaluation.Evaluator4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement) 
> at Microsoft.Build.Evaluation.Evaluator4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport) 
> at Microsoft.Build.Evaluation.Evaluator4.EvaluateImportElement(String directoryOfImportingFile, ProjectImportElement importElement) 
> at Microsoft.Build.Evaluation.Evaluator4.PerformDepthFirstPass(ProjectRootElement currentProjectOrImport) 
> at Microsoft.Build.Evaluation.Evaluator4.Evaluate()
@AR-May AR-May deleted the lock-contention-in-ProjectRootElementCache-3 branch March 1, 2024 15:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. performance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Perf: potential lock contention in ProjectRootElementCache.Get
5 participants