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

Memory Issue: Subscribing to most framework element events causes a memory leak #3719

Closed
alexserov opened this issue Nov 30, 2020 · 2 comments
Assignees
Labels
product-winui3 WinUI 3 issues

Comments

@alexserov
Copy link

Describe the bug
When a FrameworkElement descendant subscribes to its own event, WinUI internals creates a strong handle to the delegate and the event owner. As a result, the framework element cannot be collected.


Subscription sample:

public class FakeControl : Control {
    public FakeControl() {
        this.SizeChanged += FakeControl_SizeChanged;
    }

    private void FakeControl_SizeChanged(object sender, SizeChangedEventArgs e) {
        throw new NotImplementedException();
    }
}    

WinDbg & SOS output:

0:023> !dumpheap -type FakeControl
 Address       MT     Size
055e130c 15c7b958       64     

Statistics:
      MT    Count    TotalSize Class Name
15c7b958        1           64 App5.FakeControl
Total 1 objects
0:023> !gcroot 055e130c
HandleTable:
    02DD116C (strong handle)
    -> 055E8EBC Microsoft.UI.Xaml.SizeChangedEventHandler
    -> 055E8E08 System.Runtime.CompilerServices.Closure
    -> 055E8E18 System.Object[]
    -> 055E791C WinRT.EventSource`1[[Microsoft.UI.Xaml.SizeChangedEventHandler, Microsoft.WinUI]]
    -> 055E646C Microsoft.UI.Xaml.SizeChangedEventHandler
    -> 055E130C App5.FakeControl

    02DD1170 (strong handle)
    -> 055E130C App5.FakeControl

Found 2 unique roots (run '!gcroot -all' to see all roots).

I tried to subscribe to an event with the weak event handler as follows:

public class FakeControl2 : Control {
    public FakeControl2() {
        var handler = new WeakSizeChangedHandler<FakeControl2>(this, (x, sender, e) => x.FakeControl2_SizeChanged(sender, e));
        this.SizeChanged += handler.OnEvent;
    }
    private void FakeControl2_SizeChanged(object sender, SizeChangedEventArgs e) {
        throw new NotImplementedException();
    }
}
class WeakSizeChangedHandler<TTarget> where TTarget : class
{
    WeakReference<TTarget> reference;
    Action<TTarget, object, SizeChangedEventArgs> onEvent;
    public WeakSizeChangedHandler(TTarget target, Action<TTarget, object, SizeChangedEventArgs> onEvent) {
        this.reference = new WeakReference<TTarget>(target);
        this.onEvent = onEvent;
    }
    public void OnEvent(object sender, SizeChangedEventArgs e) {
        if(reference.TryGetTarget(out var target))            
            onEvent(target, sender, e);            
    }
}

But still, there's a remaining strong handle:

0:023> !dumpheap -type FakeControl
 Address       MT     Size
051e130c 158cb958       64     
051e6474 158ce6c0       12     
051e6480 158cea34       32     
051e64a0 158ce624       16     
051e64b0 0cc40318       12     

Statistics:
      MT    Count    TotalSize Class Name
158ce6c0        1           12 App5.FakeControl2+<>c
0cc40318        1           12 System.WeakReference`1[[App5.FakeControl2, App5]]
158ce624        1           16 App5.WeakSizeChangedHandler`1[[App5.FakeControl2, App5]]
158cea34        1           32 System.Action`3[[App5.FakeControl2, App5],[System.Object, System.Private.CoreLib],[Microsoft.UI.Xaml.SizeChangedEventArgs, Microsoft.WinUI]]
158cb958        1           64 App5.FakeControl2
Total 5 objects
0:023> !DumpHeap /d -mt 158cb958
 Address       MT     Size
051e130c 158cb958       64     

Statistics:
      MT    Count    TotalSize Class Name
158cb958        1           64 App5.FakeControl2
Total 1 objects
0:023> !gcroot 051e130c
HandleTable:
    04C0116C (strong handle)
    -> 051E130C App5.FakeControl2

Found 1 unique roots (run '!gcroot -all' to see all roots).

In my real application, I cannot create any controls that track their properties by using event handlers.


Steps to reproduce the bug

Steps to reproduce the behavior:

  1. Download the App5 project (you can find a link to the attachment in the Additional context section below)
  2. Build and run the project
  3. Click on the Click Me button
  4. Create a memory snapshot and analyze it (I use the WinDbg since it is more informative for me)

Expected behavior
All of the FakeControl instances should be collectible.

Screenshots

Version Info

NuGet package version: [Microsoft.WinUI 3.0.0-preview3.201113.0]

Windows app type:

UWP Win32
Yes
Windows 10 version Saw the problem?
Insider Build (xxxxx)
May 2020 Update (19041) Yes
November 2019 Update (18363)
May 2019 Update (18362)
October 2018 Update (17763)
April 2018 Update (17134)
Fall Creators Update (16299)
Creators Update (15063)
Device form factor Saw the problem?
Desktop Yes
Xbox
Surface Hub
IoT

Additional context

App5.zip

@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Nov 30, 2020
@Scottj1s Scottj1s self-assigned this Nov 30, 2020
@Scottj1s
Copy link
Member

looks like a dupe of:
microsoft/CsWinRT#413

@Scottj1s
Copy link
Member

Scottj1s commented Apr 2, 2021

This has been resolved with CsWinRT 1.2, which will be included in Project Reunion 0.5.1

@Scottj1s Scottj1s closed this as completed Apr 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
product-winui3 WinUI 3 issues
Projects
None yet
Development

No branches or pull requests

3 participants