-
Notifications
You must be signed in to change notification settings - Fork 840
Dev/xygu/20251216/hotreload-fwktemplate-leak #22161
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
base: master
Are you sure you want to change the base?
Conversation
|
todo: tests |
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.
Pull request overview
This PR addresses memory leaks in the Uno Platform's template system by introducing weak references for delegate storage and optimizing template pooling. The changes prevent FrameworkTemplate instances from keeping view factories alive unnecessarily and avoid creating unnecessary pool entries when pooling is disabled.
Key Changes:
- Introduces a new
WeakDelegate<TDelegate>wrapper class to hold delegates with weak references to their targets - Updates
FrameworkTemplateto store its view factory usingWeakDelegateinstead of direct delegate references - Optimizes
FrameworkTemplatePoolto skip creating pool entries when pooling is disabled
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
src/Uno.UI/Helpers/WeakDelegate.cs |
New helper class implementing weak delegate pattern to prevent memory leaks by holding delegate targets weakly |
src/Uno.UI/UI/Xaml/FrameworkTemplate.cs |
Updates view factory storage to use WeakDelegate wrapper and fixes typo in XAML scope comment |
src/Uno.UI/UI/Xaml/FrameworkTemplatePool.cs |
Refactors pool management to use TryGetTemplatePool and skip pool entry creation when pooling is disabled, plus fixes error message typo |
Critical Issues Found:
- Inverted logic in
TryGetTemplatePoolat line 320 - returns null when pooling IS enabled instead of when disabled - Non-existent API -
HasSingleTargetdoesn't exist on theDelegateclass; should useGetInvocationList().Length == 1 - Missing issue reference in PR description - the PR must link to a GitHub issue per repository guidelines
|
π€ Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22161/wasm-skia-net9/index.html |
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
|
32fa54f to
c158701
Compare
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
|
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.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
| ο»Ώ#nullable enable | ||
|
|
||
| using System; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using System.Reflection; | ||
|
|
||
| namespace Uno.UI.Helpers; | ||
|
|
||
| internal interface IDelegate<TDelegate> where TDelegate : Delegate | ||
| { | ||
| public object? Target { get; } | ||
| public MethodInfo Method { get; } | ||
| public TDelegate? Delegate { get; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Represents a plain wrapper over a normal delegate instance. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Solely used to conform to the <see cref="IDelegate{TDelegate}"/> interface, | ||
| /// so <see cref="WeakDelegate{TDelegate}"/> and literal delegate can be used interchangeably. | ||
| /// </remarks> | ||
| /// <typeparam name="TDelegate">The type of delegate encapsulated by this instance. Must derive from <see cref="Delegate"/>.</typeparam> | ||
| internal class LiteralDelegate<TDelegate> : IDelegate<TDelegate> | ||
| where TDelegate : Delegate | ||
| { | ||
| public object? Target => Delegate!.Target; | ||
| public MethodInfo Method => Delegate!.Method; | ||
| public TDelegate? Delegate { get; } | ||
|
|
||
| public LiteralDelegate(TDelegate d) | ||
| { | ||
| Delegate = d; | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Represents a delegate that references its <see cref="Delegate.Target"/> weakly, allowing the target to be eventually garbage collected. | ||
| /// </summary> | ||
| /// <remarks>Use <see cref="WeakDelegate{TDelegate}"/> to hold a reference to a delegate without preventing its | ||
| /// target object from being collected by the garbage collector. This is useful for scenarios where you want to | ||
| /// avoid memory leaks caused by strong references. Only delegates with a single target are supported; | ||
| /// multicast delegates are not allowed.</remarks> | ||
| /// <typeparam name="TDelegate">The type of delegate to be referenced. Must derive from <see cref="Delegate"/>.</typeparam> | ||
| internal class WeakDelegate<TDelegate> : IDelegate<TDelegate> | ||
| where TDelegate : Delegate | ||
| { | ||
| public WeakReference? Instance { get; init; } | ||
| public MethodInfo Method { get; init; } | ||
|
|
||
| // static method delegate doesn't capture any target instance, so we can just reuse it | ||
| private TDelegate? _staticDelegate; | ||
|
|
||
| public WeakDelegate(TDelegate d) | ||
| { | ||
| if (!d.HasSingleTarget) | ||
| { | ||
| throw new NotImplementedException("Multi-cast delegate not supported"); | ||
| } | ||
|
|
||
| if (d.Target is { } t) | ||
| { | ||
| Instance = new WeakReference(t); | ||
| Method = d.Method; | ||
| } | ||
| else | ||
| { | ||
| _staticDelegate = d; | ||
| Method = d.Method; // still used outside of this class for logging | ||
| } | ||
| } | ||
|
|
||
| public object? Target => Instance?.Target; | ||
|
|
||
| /// <summary> | ||
| /// Gets the delegate instance that targets the referenced object and method, | ||
| /// may be null if the target is no longer available. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// DO NOT store/cache the returned delegate, as it keeps a strong reference to the target just like the original delegate. | ||
| /// </remarks> | ||
| public TDelegate? Delegate => | ||
| _staticDelegate ?? | ||
| // for instanced method delegate, only try to create if the instance reference is still alive | ||
| (Instance!.Target is { } t | ||
| ? System.Delegate.CreateDelegate(typeof(TDelegate), t, Method) as TDelegate | ||
| : null); | ||
| } | ||
|
|
||
| internal class DelegateHelper | ||
| { | ||
| public static LiteralDelegate<TDelegate> CreateLiteral<TDelegate>(TDelegate d) | ||
| where TDelegate : Delegate | ||
| { | ||
| return new(d); | ||
| } | ||
|
|
||
| public static WeakDelegate<TDelegate> CreateWeak<TDelegate>(TDelegate d) | ||
| where TDelegate : Delegate | ||
| { | ||
| return new(d); | ||
| } | ||
| } |
Copilot
AI
Dec 19, 2025
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.
The new WeakDelegate class lacks test coverage. Given that this is a critical component for preventing memory leaks and involves complex logic around weak references, delegate recreation, and handling both static and instance methods, consider adding unit tests to verify its behavior, especially edge cases like delegate recreation after garbage collection and proper handling of static vs instance delegates.
|
π€ Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22161/wasm-skia-net9/index.html |
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
π€ Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22161/wasm-skia-net9/index.html |
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
|
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.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
|
π€ Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22161/wasm-skia-net9/index.html |
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
|
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.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
|
|
|
|
4f3c464 to
87e15a2
Compare
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
87e15a2 to
b9b8204
Compare
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.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_FrameworkTemplatePool.cs
Outdated
Show resolved
Hide resolved
|
π€ Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22161/wasm-skia-net9/index.html |
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
|
b9b8204 to
9d17969
Compare
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
π€ Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22161/wasm-skia-net9/index.html |
|
|
|
/azp run |
|
Azure Pipelines successfully started running 2 pipeline(s). |
|
π€ Your WebAssembly Skia Sample App stage site is ready! Visit it here: https://unowasmprstaging.z20.web.core.windows.net/pr-22161/wasm-skia-net9/index.html |
|
π€ Your Docs stage site is ready! Visit it here: https://unodocsprstaging.z13.web.core.windows.net/pr-22161/docs/index.html |
|
|
GitHub Issue: closes unoplatform/uno-private#1670
PR Type:
What is the current behavior? π€
What is the new behavior? π
PR Checklist β
Please check if your PR fulfills the following requirements:
Screenshots Compare Test Runresults.Other information βΉοΈ
view-factory method are usually generated with
staticmodifier, except when hot-reload is enable (which implies in DEBUG only)