-
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 memory leak in ProjectRootElement.Reload #6457
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
using System.Security.AccessControl; | ||
using System.Security.Principal; | ||
#endif | ||
using System.Reflection; | ||
using System.Text; | ||
using System.Threading; | ||
using System.Xml; | ||
|
@@ -18,6 +19,7 @@ | |
|
||
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; | ||
using ProjectCollection = Microsoft.Build.Evaluation.ProjectCollection; | ||
using Shouldly; | ||
using Xunit; | ||
|
||
namespace Microsoft.Build.UnitTests.OM.Construction | ||
|
@@ -1854,6 +1856,31 @@ public void ReloadCanOverwriteUnsavedChanges() | |
AssertReload(SimpleProject, ComplexProject, true, true, true, act); | ||
} | ||
|
||
[Fact] | ||
public void ReloadDoesNotLeakCachedXmlDocuments() | ||
{ | ||
using var env = TestEnvironment.Create(); | ||
var testFiles = env.CreateTestProjectWithFiles("", new[] { "build.proj" }); | ||
var projectFile = testFiles.CreatedFiles.First(); | ||
|
||
var projectElement = ObjectModelHelpers.CreateInMemoryProjectRootElement(SimpleProject); | ||
projectElement.Save(projectFile); | ||
|
||
int originalDocumentCount = GetNumberOfDocumentsInProjectStringCache(projectElement); | ||
|
||
// Test successful reload. | ||
projectElement.Reload(false); | ||
GetNumberOfDocumentsInProjectStringCache(projectElement).ShouldBe(originalDocumentCount); | ||
|
||
// Test failed reload. | ||
using (StreamWriter sw = new StreamWriter(projectFile)) | ||
{ | ||
sw.WriteLine("<XXX />"); // Invalid root element | ||
} | ||
Should.Throw<InvalidProjectFileException>(() => projectElement.Reload(false)); | ||
GetNumberOfDocumentsInProjectStringCache(projectElement).ShouldBe(originalDocumentCount); | ||
} | ||
|
||
private void AssertReload( | ||
string initialContents, | ||
string changedContents, | ||
|
@@ -1986,5 +2013,17 @@ private void VerifyAssertLineByLine(string expected, string actual) | |
{ | ||
Helpers.VerifyAssertLineByLine(expected, actual, false); | ||
} | ||
|
||
/// <summary> | ||
/// Returns the number of documents retained by the project string cache. | ||
/// Peeks at it via reflection since internals are not visible to these tests. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason internals aren't visible to these tests? That seems like something we'd want for other tests later, too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe it's by design. These tests should be testing the public surface (or Object Model, as in Build.OM.UnitTests). I'm not confident that this test case belongs here. It is testing a public method but needs an internal hook to verify the expected behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It sounds a little better to me to move it to Build.UnitTests, but it isn't too important. |
||
/// </summary> | ||
private int GetNumberOfDocumentsInProjectStringCache(ProjectRootElement project) | ||
{ | ||
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetProperty; | ||
object document = typeof(ProjectRootElement).InvokeMember("XmlDocument", bindingFlags, null, project, Array.Empty<object>()); | ||
object cache = document.GetType().InvokeMember("StringCache", bindingFlags, null, document, Array.Empty<object>()); | ||
return (int)cache.GetType().InvokeMember("DocumentCount", bindingFlags, null, cache, Array.Empty<object>()); | ||
} | ||
} | ||
} |
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.
I'm wondering if we should verify that the project is the new one the first time and the old one the second time. Not directly related to your change, though.
More related, I'd be in favor of reloading 3-4 times just in case there's something unexpected.