[leak-fix] Fix TransformGroup.Children memory leak (Fixes #36367) - #36531
[leak-fix] Fix TransformGroup.Children memory leak (Fixes #36367)#36531github-actions[bot] wants to merge 1 commit into
Conversation
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36531Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36531" |
@MauiBot The existing-child subscription gap is fixed at head |
This comment has been minimized.
This comment has been minimized.
@MauiBot The assigned-child suggestion is already addressed in the cited head |
This comment has been minimized.
This comment has been minimized.
@MauiBot The current-head review reports zero errors, warnings, suggestions, inline findings, or actionable code issues and confirms the prior major finding is fixed. Its discussion verdict is only unavailable or pending required CI, so no no-op code change is warranted. Ready for another review. |
This comment has been minimized.
This comment has been minimized.
|
🔍 AI-generated update (GitHub Copilot CLI, on behalf of @kubaflo) @MauiBot Implemented the remove/reset coverage warning in
All 5 focused |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
|
🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo) @MauiBot Addressed the current replace-path coverage finding in |
This comment has been minimized.
This comment has been minimized.
|
🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo) @MauiBot Review No code change is warranted for the warning: |
This comment has been minimized.
This comment has been minimized.
|
🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo) @MauiBot Review |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Fixes a managed memory leak in TransformGroup where a shared/long-lived TransformCollection could strongly root the owning TransformGroup via event subscriptions, and adds focused unit tests to prevent regressions in both leak behavior and invalidation behavior.
Changes:
- Replaces strong
CollectionChanged/ per-childPropertyChangedsubscriptions with weak-event proxies managed by a privateChildrenSubscriptionshelper (with finalizer-based cleanup). - Updates collection-change handling to keep per-child subscriptions in sync across add/remove/replace/reset/move operations.
- Adds
TransformGroupMemoryTestscovering leak prevention and ensuring invalidation still works after GC and across collection mutations.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Controls/src/Core/Shapes/TransformGroup.cs | Switches to weak subscription proxies for Children and child transforms to prevent retention via shared collections. |
| src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs | Adds regression tests validating the leak fix and subscription lifecycle behavior across collection operations and GC. |
|
🔍 AI-generated update (GitHub Copilot CLI, on behalf of @kubaflo) @copilot-pull-request-reviewer, the null collection/item finding is fixed in f5cb57d with two red-to-green regressions, and the PR description now documents the behavior and 10-test focused suite. Ready for re-review. |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@github-actions[bot] — new AI review results are available based on this last commit:
f5cb57d. To request a fresh review after new comments or commits, comment/review rerun.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ✅ PASSED
Platform: ANDROID · Base: main · Merge base: 0395a53b
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
🧪 TransformGroupMemoryTests TransformGroupMemoryTests |
✅ FAIL — 146s | ✅ PASS — 116s |
🔴 Without fix — 🧪 TransformGroupMemoryTests: FAIL ✅ · 146s
Error-relevant lines (filtered from the build log):
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.ExistingChildTransformChangesStillInvalidateAfterGc() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 86
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.ReassigningChildrenMovesChangeSubscriptions() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 127
at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.GetChildProxies(TransformGroup group) in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 23
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.MovingChildTransformsReusesSubscriptions() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 187
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.TransformGroupDoesNotLeakWhenSharingChildren() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 50
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.GetChildProxyReference(TransformGroup group, Int32 index) in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 16
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.ClearingChildrenReleasesSubscriptionsAndAllowsReuse() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 219
at Microsoft.Maui.Controls.Shapes.TransformGroup.UpdateTransformMatrix() in /_/src/Controls/src/Core/Shapes/TransformGroup.cs:line 76
at Microsoft.Maui.Controls.Shapes.TransformGroup.OnTransformGroupChanged(BindableObject bindable, Object oldValue, Object newValue) in /_/src/Controls/src/Core/Shapes/TransformGroup.cs:line 47
at Microsoft.Maui.Controls.BindableObject.OnBindablePropertySet(BindableProperty property, Object original, Object value, Boolean didChange, Boolean willFirePropertyChanged) in /_/src/Controls/src/Core/BindableObject.cs:line 701
at Microsoft.Maui.Controls.BindableObject.SetValueActual(BindableProperty property, BindablePropertyContext context, Object value, Boolean currentlyApplying, SetValueFlags attributes, SetterSpecificity specificity, Boolean silent) in /_/src/Controls/src/Core/BindableObject.cs:line 688
at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes, SetterSpecificity specificity) in /_/src/Controls/src/Core/BindableObject.cs:line 616
at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value) in /_/src/Controls/src/Core/BindableObject.cs:line 521
at Microsoft.Maui.Controls.Shapes.TransformGroup.set_Children(TransformCollection value) in /_/src/Controls/src/Core/Shapes/TransformGroup.cs:line 31
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.NullChildrenUseIdentityMatrix() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 244
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.RemovingChildTransformReleasesSubscription() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 139
at Microsoft.Maui.Controls.Shapes.TransformGroup.UpdateTransformMatrix() in /_/src/Controls/src/Core/Shapes/TransformGroup.cs:line 77
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.NullChildIsIgnoredWhenUpdatingMatrix() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 256
at Microsoft.Maui.Controls.Core.UnitTests.TransformGroupMemoryTests.ReplacingChildTransformReleasesOldSubscription() in /_/src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs:line 163
🟢 With fix — 🧪 TransformGroupMemoryTests: PASS ✅ · 116s
(no coded error found; showing last 1200 chars)
18.0.1 (x64)
Starting test execution, please wait...
A total of 1 test files matched the specified pattern.
[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.8.2+699d445a1a (64-bit .NET 10.0.0)
[xUnit.net 00:00:00.22] Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.91] Discovered: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.94] Starting: Microsoft.Maui.Controls.Core.UnitTests
Passed ExistingChildTransformChangesStillInvalidateAfterGc [96 ms]
Passed ReassigningChildrenMovesChangeSubscriptions [14 ms]
Passed ChildTransformChangesStillInvalidateAfterGc [51 ms]
Passed MovingChildTransformsReusesSubscriptions [36 ms]
Passed TransformGroupDoesNotLeakWhenSharingChildren [116 ms]
Passed ClearingChildrenReleasesSubscriptionsAndAllowsReuse [149 ms]
Passed NullChildrenUseIdentityMatrix [< 1 ms]
Passed RemovingChildTransformReleasesSubscription [80 ms]
Passed NullChildIsIgnoredWhenUpdatingMatrix [< 1 ms]
[xUnit.net 00:00:02.69] Finished: Microsoft.Maui.Controls.Core.UnitTests
Passed ReplacingChildTransformReleasesOldSubscription [62 ms]
Test Run Successful.
Total tests: 10
Passed: 10
Total time: 3.4414 Seconds
📁 Fix files reverted (1 files)
src/Controls/src/Core/Shapes/TransformGroup.cs
📱 UI Tests — Shape
Detected UI test categories: Shape
✅ Deep UI tests — 35 passed, 0 failed across 1 category on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
Shape |
35/35 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
📋 Pre-Flight — Context & Validation
Issue: #36367 - [leak-scan] TransformGroup.Children — shared TransformCollection strongly roots the TransformGroup via CollectionChanged
PR: #36531 - [leak-fix] Fix TransformGroup.Children memory leak (Fixes #36367)
Platforms Affected: All platforms; managed Controls code. Test platform requested for this run: android.
Files Changed: 1 implementation, 1 test
Key Findings
- Issue #36367 reports a strong
TransformCollection.CollectionChanged += group.OnChildrenCollectionChangedsubscription that keepsTransformGroupalive when the collection is shared or long-lived. - PR #36531 changes
src/Controls/src/Core/Shapes/TransformGroup.csto route collection and child transform subscriptions through weak event proxies, and adds focused unit regression coverage inTransformGroupMemoryTests. - Public issue comments were empty. PR discussion showed prior MauiBot concerns around pre-populated collections, replace/move coverage, and reassignment coverage; current code/test coverage addresses those points.
- Test type detected: Controls unit tests (
src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj) focused onTransformGroupMemoryTests. No UI test category is directly impacted.
Code Review Summary
Verdict: NEEDS_DISCUSSION
Confidence: low
Errors: 0 | Warnings: 0 | Suggestions: 0
Key code review findings:
- Code review found no actionable implementation findings.
- Prior MauiBot issue "assigned pre-populated
TransformCollectionchildren were not subscribed" is fixed by subscribing current children inChildrenSubscriptions.Subscribe. - Prior MauiBot requests for replace/move/reassignment coverage are covered by current regression tests.
- CI status was undetermined because
ghis unauthenticated in this environment; confidence remains low per code-review rules.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #36531 | Use WeakNotifyCollectionChangedProxy for Children.CollectionChanged plus WeakNotifyPropertyChangedProxy instances for each child transform, tracked by a nested ChildrenSubscriptions helper. |
✅ PASSED (Gate) | TransformGroup.cs, TransformGroupMemoryTests.cs |
Original PR fix; gate result was supplied as already passed. |
🔬 Code Review — Deep Analysis
Code Review — PR #36531
Independent Assessment
What this changes: TransformGroup now uses weak collection/property changed proxies for Children and child transforms, and handles null/empty children safely while preserving matrix invalidation.
Inferred motivation: Prevent shared/long-lived TransformCollection instances from strongly rooting TransformGroup.
Reconciliation with PR Narrative
Author claims: Fixes TransformGroup.Children memory leak from #36367 using weak subscriptions and regression tests.
Agreement/disagreement: Agrees. The implementation matches the claimed root cause and preserves child mutation behavior.
Prior Review Reconciliation
| Prior Error Finding | Source | Status | Evidence |
|---|---|---|---|
Assigned pre-populated TransformCollection children were not subscribed |
MauiBot [major] inline |
Fixed | Subscribe() now adds existing children at TransformGroup.cs:139-145; covered by TransformGroupMemoryTests.cs:71-88. |
| Replace/Move mutation coverage missing | MauiBot [moderate] inline |
Fixed | Replace covered at TransformGroupMemoryTests.cs:154-176; Move at 178-208. |
| Reassignment test did not prove old subscriptions removed | MauiBot [moderate] inline |
Fixed | Old child/collection mutations are asserted not to change value at TransformGroupMemoryTests.cs:113-119. |
Blast Radius Assessment
- Runs for all instances: yes — all
TransformGroup.Childrensubscriptions use this path. - Startup impact: no — no static/startup code.
- Static/shared state: no.
CI Status
- Required-check result: pending/undetermined.
- Classification: undetermined.
- Action taken:
gh pr checks --requiredunavailable due missing auth; public check data was not sufficient for required-check classification. Confidence capped low; no LGTM.
Findings
No Error, Warning, or Suggestion findings.
Failure-Mode Probing
- Shared collection outlives group: weak proxy breaks strong root; finalizer unsubscribes lingering proxy.
- Assigned collection already has children: existing children are subscribed in
Subscribe(). - Remove/replace/clear/move: subscriptions are removed/reused appropriately; tests cover each path.
- Null
Children/null child: guarded and tested; identity matrix/ignored child behavior is safe.
Verdict: NEEDS_DISCUSSION
Confidence: low
Summary: Code review found no actionable implementation issues, and prior findings appear fixed. However CI is still pending/undetermined, so the skill rules prohibit LGTM until required checks complete.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix-1 | Collection-owned aggregate invalidation event in TransformCollection; TransformGroup listens to one weak event. |
✅ PASS | 3 files | Mechanically passed 10 focused unit tests, but self-review found broad global behavior risk. |
| 2 | try-fix-2 | Static per-source weak observer registry in TransformGroup, with weak group references and duplicate-aware local child membership. |
✅ PASS | 2 files | Passed 12 focused unit tests, but self-review found high complexity/global-state risk. |
| 3 | try-fix-3 | Weak collection proxy plus direct child PropertyChanged subscriptions filtered by current membership. |
❌ FAIL | 2 files | Failed the core leak test; child transform still roots the group through direct PropertyChanged. |
| PR | PR #36531 | TransformGroup owns weak collection and per-child property proxies through private ChildrenSubscriptions. |
✅ PASSED (Gate) | 2 files | Original PR fix; gate result supplied as already passed. |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| gpt-5.5 expert reviewer | 1 | Yes | Candidate 1: move child observation into TransformCollection. |
| gpt-5.5 expert reviewer | 1 | Yes | Candidate 2: static per-source weak observer registry localized to TransformGroup. |
| gpt-5.5 expert reviewer | 1 | Yes | Candidate 3: weak collection proxy only, with direct child subscriptions guarded by membership checks. |
| gpt-5.5 expert reviewer | 2 | No | NO NEW IDEAS: any viable local fix reduces to weak collection subscription plus weak per-child property subscriptions; excluded alternatives are broader, more complex, or empirically failing. |
Exhausted: Yes
Selected Fix: PR #36531 — It passes the supplied gate and focused regression tests while keeping the blast radius localized to TransformGroup. Passing alternatives were broader or more complex; the simpler weak-collection-only alternative failed the leak test.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current description is accurate and high quality, but the title uses a workflow/noise prefix instead of the required component-focused title format.
Recommended title
[All] Shapes: Fix TransformGroup.Children memory leak
Recommended description
> 🤖 This pull request was generated automatically by the **Memory Leak Fixer** agentic workflow. It contains an empirically-validated regression test (red→green) plus a managed product fix.
Fixes #36367
Refs: dotnet/maui#36367
Target branch: main
Attempt: 1/3
## The leak
`TransformGroup` subscribed to its `Children` (`TransformCollection`) via a plain
`CollectionChanged += instanceMethod` handler, and to each child `Transform` via a plain
`PropertyChanged += instanceMethod` handler. Because these are strong delegates, a **shared or
long-lived `TransformCollection`** keeps the `TransformGroup` alive: the collection's event holds
a strong reference back to the group, so the group can never be collected while the collection
lives. The teardown (`-=`) only happened when `Children` was reassigned, never when the group
itself was dropped.
## The fix
`src/Controls/src/Core/Shapes/TransformGroup.cs` now routes both subscriptions through the
existing weak-event helpers used elsewhere in the codebase:
- `WeakNotifyCollectionChangedProxy` for the collection's `CollectionChanged`
- `WeakNotifyPropertyChangedProxy` for each child's `PropertyChanged`
These are managed by a small nested `ChildrenSubscriptions` class that also has a finalizer
(`~ChildrenSubscriptions() => UnsubscribeAll()`), mirroring the pattern used by other
`WeakEventProxy` owners. The group no longer roots itself through a shared collection, and child
add/remove/replace/reset are still tracked so transform invalidation continues to work. Move
operations reuse existing child proxies. Matrix recomputation also treats a null `Children`
collection and null collection entries as empty.
The change is fully managed and cross-platform (`src/Controls/src`).
## Regression test
Added `src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cs` with ten tests:
- `TransformGroupDoesNotLeakWhenSharingChildren` — assigns a shared `TransformCollection` to a
`TransformGroup`, drops the group, and asserts it is collected. This is the genuine leak check.
- `ChildTransformChangesStillInvalidateAfterGc` — verifies that after a GC, changing a child
transform still invalidates the group's `Value` (guards against the weak subscriptions being
collected too eagerly).
- `ExistingChildTransformChangesStillInvalidateAfterGc` — assigns a pre-populated
`TransformCollection` and verifies changing an existing child still updates the group after GC.
- `ReassigningChildrenMovesChangeSubscriptions` — replaces the full collection, verifies old
collection/child mutations no longer invalidate the group, and confirms replacement mutations
remain active.
- `RemovingChildTransformReleasesSubscription` — removes one child while keeping it alive, then
verifies its weak proxy is collected and the retained child still invalidates the group.
- `ReplacingChildTransformReleasesOldSubscription` — replaces a child while keeping the old child
alive, verifies the old weak proxy is collected, and confirms the replacement still invalidates
the group.
- `MovingChildTransformsReusesSubscriptions` — moves existing children, verifies the matrix changes
for the new order, and confirms the existing weak proxies continue to invalidate after GC.
- `ClearingChildrenReleasesSubscriptionsAndAllowsReuse` — clears all children, verifies both weak
proxies are collected, and confirms a subsequently added child still invalidates after GC.
- `NullChildrenUseIdentityMatrix` — treats a null `Children` collection as empty and preserves the
identity matrix.
- `NullChildIsIgnoredWhenUpdatingMatrix` — skips null entries when recomputing the transform matrix.
## Platforms
The product change is managed Controls code and applies to all platforms. The supplied regression
gate for this review passed on Android.
🏁 Report — Final Recommendation
Comparative Fix Report — PR #36531
Inputs
- Issue: #36367,
TransformGroup.Childrenmemory leak from strong event subscriptions. - Supplied gate: ✅ PASSED — focused regression tests fail without the fix and pass with the fix on Android.
- Expert PR review: no actionable inline findings;
inline-findings.jsonis an empty JSON array.
Candidate ranking
| Rank | Candidate | Regression result | Assessment |
|---|---|---|---|
| 1 | pr |
✅ PASSED | Best candidate. It directly fixes both known retention paths with weak collection and weak per-child property subscriptions, keeps the behavior localized to TransformGroup, and has focused tests for leak prevention plus add/remove/replace/reset/move/null behavior. |
| 2 | pr-plus-reviewer |
✅ PASSED | Equivalent to pr. The expert reviewer found no actionable changes to apply, so this candidate has the same code and risk profile as the raw PR fix. Ranked just below pr because it adds no implementation improvement. |
| 3 | try-fix-1 |
✅ PASS | Functionally passed the focused tests, but moves child observation into TransformCollection via an internal aggregate event. That broadens behavior for every TransformCollection consumer and introduces global collection-owned observation that is unnecessary for this localized leak. |
| 4 | try-fix-2 |
✅ PASS | Functionally passed the focused tests, including duplicate/shared-observer scenarios, but relies on a static per-source weak observer registry with ConditionalWeakTable entries and weak group dispatch. It is substantially more complex than the PR fix and introduces global static observer state for a local ownership problem. |
| 5 | try-fix-3 |
❌ FAIL | Must rank below all passing candidates. It keeps direct child PropertyChanged subscriptions, so a shared collection can keep a child alive and the child can still strongly root the TransformGroup; the core leak test failed. |
Comparison details
pr and pr-plus-reviewer solve the precise issue while preserving existing TransformGroup ownership boundaries. ChildrenSubscriptions owns the weak collection proxy and child proxies for one group instance, releases old collection/child subscriptions on reassignment and mutation, and avoids affecting unrelated TransformCollection consumers.
try-fix-1 is viable under the focused tests, but it changes the responsibility of TransformCollection itself. That means every collection now observes every child transform, even when no TransformGroup needs that aggregate invalidation. The broader blast radius is not justified when the leak can be fixed inside TransformGroup.
try-fix-2 is also viable under the focused tests, but the static weak registry adds lifecycle complexity, pruning requirements, global state, and dispatch indirection. It is harder to maintain and reason about than per-instance weak proxies.
try-fix-3 is not viable because it fails the regression suite. Its direct child subscriptions leave the child-transform retention path intact.
Winning candidate
Winner: pr
The submitted PR fix is the best balance of correctness, test coverage, and blast-radius control. It passes the supplied regression gate, the expert reviewer found no actionable defects, and the passing try-fix alternatives are broader or more complex without improving behavior.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
|
🔍 AI-generated review follow-up (GitHub Copilot CLI, on behalf of @kubaflo) @MauiBot Review 4707195586 passed the gate on f5cb57d, reports 0 errors, 0 warnings, and 0 suggestions, and ranks the submitted PR as the best candidate. The null collection/item lifecycle findings are confirmed fixed. No code change or redundant rerun is warranted; the |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
|
🔍 AI-generated merge-readiness analysis (GitHub Copilot CLI, on behalf of @kubaflo) Good for merge from the automated CI perspective. The completed failures are unrelated to the
No failure implicates |
This comment has been minimized.
This comment has been minimized.
Tests Failure Analysis
Test Failure Review: Not ready - click to expandOverall verdict: Not ready — 2 build legs are deterministic regressions vs
Coverage: 161 checks · 155 passing · 6 failing · 0 pending · 0 inaccessible · 1 unmapped · 5 unexplained build legs · 0 unaccounted failing checks · 1 aborted failing checks · 0 canceled-build checks · 7 device-test unverified · 4 unattributed · 2 regressed-vs-base. Deterministic ceiling: Not ready — 2 legs regressed vs base plus unexplained legs, an aborted check, unconfirmed device-test greens, and unattributed failures. Builds (this PR): maui-pr-devicetests 1511233, maui-pr-uitests 1511231. Base sampling (main, 5 recent builds per definition): devicetests 1503431, 1503150; uitests 1503618, 1503332. Recommended actionHave a human inspect the 2 regressed-vs-base build legs (Windows device tests and the Gradle init-script step) and confirm the 7 unverified device-test greens before merging. |
|
🔍 AI-generated follow-up analysis (GitHub Copilot CLI, on behalf of @kubaflo) The
Main build 1511229 succeeded and all ten new |
Rebased onto inflight/current, which already contains #36150's child-subscription fix using strong CollectionChanged/PropertyChanged subscriptions — the exact mechanism #36367 reports (a shared/long-lived TransformCollection roots the TransformGroup). Converts those subscriptions to WeakNotifyCollectionChangedProxy / WeakNotifyPropertyChangedProxy, preserving #36150's Clear()/Reset teardown (guarded by Shapes/TransformGroupTests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: e27685d0-fe80-460a-aa05-83d2ab9bf032
|
🔍 AI-generated (GitHub Copilot CLI, on behalf of @kubaflo) Rebased onto |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Fixes #36367
Refs: #36367
Target branch: main
Attempt: 1/3
The leak
TransformGroupsubscribed to itsChildren(TransformCollection) via a plainCollectionChanged += instanceMethodhandler, and to each childTransformvia a plainPropertyChanged += instanceMethodhandler. Because these are strong delegates, a shared orlong-lived
TransformCollectionkeeps theTransformGroupalive: the collection's event holdsa strong reference back to the group, so the group can never be collected while the collection
lives. The teardown (
-=) only happened whenChildrenwas reassigned, never when the groupitself was dropped.
The fix
src/Controls/src/Core/Shapes/TransformGroup.csnow routes both subscriptions through theexisting weak-event helpers used elsewhere in the codebase:
WeakNotifyCollectionChangedProxyfor the collection'sCollectionChangedWeakNotifyPropertyChangedProxyfor each child'sPropertyChangedThese are managed by a small nested
ChildrenSubscriptionsclass that also has a finalizer(
~ChildrenSubscriptions() => UnsubscribeAll()), mirroring the pattern used by otherWeakEventProxyowners (e.g. the siblingGeometryGrouphardening in #36526). The group nolonger roots itself through a shared collection, and child add/remove/replace/reset are still
tracked so transform invalidation continues to work. Move operations reuse existing child proxies. Matrix recomputation also treats a null Children collection and null collection entries as empty.
The change is fully managed and cross-platform (
src/Controls/src).Regression test
Added
src/Controls/tests/Core.UnitTests/TransformGroupMemoryTests.cswith ten tests:TransformGroupDoesNotLeakWhenSharingChildren— assigns a sharedTransformCollectionto aTransformGroup, drops the group, and asserts it is collected. This is the genuine leak check.ChildTransformChangesStillInvalidateAfterGc— verifies that after a GC, changing a childtransform still invalidates the group's
Value(guards against the weak subscriptions beingcollected too eagerly).
ExistingChildTransformChangesStillInvalidateAfterGc— assigns a pre-populatedTransformCollectionand verifies changing an existing child still updates the group after GC.ReassigningChildrenMovesChangeSubscriptions— replaces the full collection, verifies old collection/child mutations no longer invalidate the group, and confirms replacement mutations remain active.RemovingChildTransformReleasesSubscription— removes one child while keeping it alive, thenverifies its weak proxy is collected and the retained child still invalidates the group.
ReplacingChildTransformReleasesOldSubscription— replaces a child while keeping the old childalive, verifies the old weak proxy is collected, and confirms the replacement still invalidates the group.
MovingChildTransformsReusesSubscriptions— moves existing children, verifies the matrix changesfor the new order, and confirms the existing weak proxies continue to invalidate after GC.
ClearingChildrenReleasesSubscriptionsAndAllowsReuse— clears all children, verifies both weakproxies are collected, and confirms a subsequently added child still invalidates after GC.
NullChildrenUseIdentityMatrix— treats a nullChildrencollection as empty and preserves the identity matrix.NullChildIsIgnoredWhenUpdatingMatrix— skips null entries while composing the remaining transforms.The cleanup-specific tests intentionally inspect private proxy instances because they verify eager detachment and collection of dead subscription objects; public matrix behavior alone cannot distinguish that from a stale weak proxy retained by a quiet source.
Initial red → green evidence (net TFM, from-source build):
Without the fix (unpatched
mainproduct source):With the fix:
Review follow-up red → green evidence:
Before subscribing transforms already present in an assigned collection:
After subscribing the collection contents during attachment:
Latest focused result:
TransformGroupMemoryTestspasses all 10 tests onnet10.0.Scope
src/Controls/src/Core/Shapes/TransformGroup.cs.ChildrenSubscriptionshelper is a private nested class).