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

🚧 Correct ancestor XAML memory leak #4236

Closed

Conversation

XAML-Knight
Copy link
Contributor

Fixes CommunityToolkit/Windows#107

Also #4187

PR Type

What kind of change does this PR introduce?

  • Bugfix (correct memory leak)

What is the current behavior?

Steps to Reproduce:

  1. Use AncestorType in some XAML content (TokenizingTextBox, for example)
  2. Load the XAML content (eg navigate to it in a frame)
  3. Unload the XAML content (eg navigate away from it, remove it from the visual tree)
  4. Notice that the XAML elements are never freed correctly, keep taking memory, and weak references will still resolve.

What is the new behavior?

The elements should be freed.

PR Checklist

Please check if your PR fulfills the following requirements:

  • Tested code with current supported SDKs
  • Pull Request has been submitted to the documentation repository instructions. Link:
  • Sample in sample app has been added / updated (for bug fixes / features)
  • New major technical changes in the toolkit have or will be added to the Wiki e.g. build changes, source generators, testing infrastructure, sample creation changes, etc...
  • Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Header has been added to all new source files (run build/UpdateHeaders.bat)
  • Contains NO breaking changes

Other information

@XAML-Knight XAML-Knight self-assigned this Sep 9, 2021
@ghost
Copy link

ghost commented Sep 9, 2021

Thanks XAML-Knight for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌

@ghost ghost requested review from michael-hawker, azchohfi and Rosuavio September 9, 2021 23:04
@ghost ghost added bug 🐛 An unexpected issue that highlights incorrect behavior extensions ⚡ labels Sep 9, 2021
@XAML-Knight XAML-Knight removed their assignment Sep 9, 2021
@XAML-Knight XAML-Knight marked this pull request as draft September 9, 2021 23:07

treeRoot.Unloaded += (sender, e) =>
{
Assert.AreEqual(grid, null);
Copy link
Member

Choose a reason for hiding this comment

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

We probably want to have a value set here that we check is set below after the await SetTestContent as well, otherwise if this doesn't fire then we never know, but we want to make sure it fires, eh?

@XAML-Knight
Copy link
Contributor Author

An issue has been raised (#4246) describing the challenges when running these unit tests via the build script. The workaround for now is to apply the [Ignore] attribute to the unit tests.

@XAML-Knight XAML-Knight changed the title 🚧 DRAFT: Correct ancestor XAML memory leak Correct ancestor XAML memory leak Sep 14, 2021
@XAML-Knight XAML-Knight marked this pull request as ready for review September 14, 2021 22:05
@michael-hawker
Copy link
Member

If I uncomment the tests, both are failing for me even locally. Though the Test Explorer seems to be very insistent about them still being ignored, had to like completely rebuild and then run in Debug mode.

I don't think the Unload event is testing what we expect to occur here. Ideally, locally at least, we want the test to fail without the fix applied and then to pass when we apply the fix.

I also noticed that we're missing unregistering the Loaded event in the Loaded event so that'll be hanging on to another reference to the parent as well:

private static void FrameworkElement_Loaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement fe)
{
SetAncestor(fe, fe.FindAscendant(GetAncestorType(fe)));
}
}

Copy link
Contributor

@Rosuavio Rosuavio left a comment

Choose a reason for hiding this comment

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

Could the issues with this test be similar to the issues we solved in the test in #4255 (commit 377a884) ?


// Need to simulate loading the control (and not just rely upon XamlReader.Load)
var frame = new Frame();
frame.Navigate(treeRoot.GetType());
Copy link
Contributor

Choose a reason for hiding this comment

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

You might need to await this by doing another await App.DispatcherQueue.EnqueueAsync()
Perhaps?

Suggested change
frame.Navigate(treeRoot.GetType());
await App.DispatcherQueue.EnqueueAsync(() => frame.Navigate(treeRoot.GetType()));


Assert.IsNull(btn.Parent);

btn.SetValue(FrameworkElementExtensions.AncestorProperty, new object());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
btn.SetValue(FrameworkElementExtensions.AncestorProperty, new object());
await App.DispatcherQueue.EnqueueAsync(() => btn.SetValue(FrameworkElementExtensions.AncestorProperty, new object()));


// Need to simulate loading the control (and not just rely upon XamlReader.Load)
var frame = new Frame();
frame.Navigate(treeRoot.GetType());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
frame.Navigate(treeRoot.GetType());
await App.DispatcherQueue.EnqueueAsync(() => frame.Navigate(treeRoot.GetType()));

var grid = treeRoot.FindChild("OuterGrid") as Grid;
var button = treeRoot.FindChild("InnerButton") as Button;

button.SetValue(FrameworkElementExtensions.AncestorProperty, new object());
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
button.SetValue(FrameworkElementExtensions.AncestorProperty, new object());
await App.DispatcherQueue.EnqueueAsync(() => button.SetValue(FrameworkElementExtensions.AncestorProperty, new object()));

}

/// <summary>
/// Attached <see cref="DependencyProperty"/> for retrieving a parent <see cref="object"/> for the <see cref="AncestorProperty"/>
/// </summary>
public static readonly DependencyProperty AncestorProperty =
DependencyProperty.RegisterAttached("Ancestor", typeof(object), typeof(FrameworkElementExtensions), new PropertyMetadata(null));
DependencyProperty.RegisterAttached("Ancestor", typeof(WeakReference<object>), typeof(FrameworkElementExtensions), new PropertyMetadata(null));
Copy link
Member

Choose a reason for hiding this comment

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

By changing the type here we break using it in XAML as intended. This can be demonstrated with the TokenizingTextBox as it uses this feature to grab the TokenSpacing property from its parent.

@michael-hawker michael-hawker marked this pull request as draft September 17, 2021 01:24
@XAML-Knight XAML-Knight changed the title Correct ancestor XAML memory leak 🚧 Correct ancestor XAML memory leak Sep 17, 2021
@michael-hawker michael-hawker removed this from the 7.1 milestone Sep 20, 2021
@michael-hawker
Copy link
Member

Closing, superseded by work done in CommunityToolkit/Windows#105

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 An unexpected issue that highlights incorrect behavior extensions ⚡ in progress 🚧
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FrameworkElementExtensions.Ancestor causes an XAML memory leak
3 participants