-
Notifications
You must be signed in to change notification settings - Fork 1.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 lock contention in ProjectRootElementCache.Get #6680
Fix lock contention in ProjectRootElementCache.Get #6680
Conversation
…ns (loading, etc) is out of a lock.
Experimental insertion in VS showed problems with these changes. Converting this PR to draft while I investigate and fix. |
…ojectRootElementCache not to insert an element to the cache.
…et function, and not in openLoader delegate function.
57cfc33
to
a6db102
Compare
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); |
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.
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?
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.
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).
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.
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.
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()
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.