[leak-fix] Fix Path.Data/RenderTransform memory leak (Fixes #35860)#36273
[leak-fix] Fix Path.Data/RenderTransform memory leak (Fixes #35860)#36273kubaflo wants to merge 4 commits into
Conversation
) Subscribe to a shared Geometry (Path.Data) and Transform (Path.RenderTransform) through weak proxies, torn down via a finalizer, so a shared/long-lived app-level PathGeometry or ScaleTransform no longer roots every Path. Adds Controls.Core.UnitTests regression tests that fail without this fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36273Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36273" |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
This PR addresses a managed memory leak in Microsoft.Maui.Controls.Shapes.Path where assigning long-lived/shared Geometry (Path.Data) or Transform (Path.RenderTransform) could keep Path instances (and therefore their visual trees/pages) alive via strong event subscriptions. The fix aligns Path with existing weak-event proxy patterns already used in Controls.
Changes:
- Replaced strong event subscriptions for
Path.DataandPath.RenderTransformwith weak-event proxies, and ensured proxies are unsubscribed during finalization. - Added new
Controls.Core.UnitTestsregression tests to ensurePathcan be collected even when the assigned geometry/transform outlives it.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/Controls/src/Core/Shapes/Path.cs | Switches Data/RenderTransform change notification wiring to weak proxies and unsubscribes proxies in ~Path() to avoid rooting Path via shared resources. |
| src/Controls/tests/Core.UnitTests/PathMemoryTests.cs | Adds GC-based regression tests validating Path does not leak when using long-lived shared Geometry/Transform. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bbcdaa9d-d75a-4e3b-b375-cf827a689644
@copilot-pull-request-reviewer Both new findings are addressed: the explicit geometry event overload is pushed in |
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.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bbcdaa9d-d75a-4e3b-b375-cf827a689644
This comment has been minimized.
This comment has been minimized.
@MauiBot The reassignment suggestion and metadata note are already addressed at current head |
This comment has been minimized.
This comment has been minimized.
@MauiBot The current-head review reports no errors or actionable findings. Its finalizer warning explicitly accepts the private lifecycle pattern and confirms the leak, reassignment, post-GC notification, and null-assignment paths are covered, so no code change is warranted. Ready for another 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
@kubaflo — new AI review results are available based on this last commit:
914e625. 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) |
|---|---|---|
🧪 PathMemoryTests PathMemoryTests |
✅ FAIL — 114s | ✅ PASS — 82s |
🔴 Without fix — 🧪 PathMemoryTests: FAIL ✅ · 114s
Determining projects to restore...
Restored /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj (in 5.78 sec).
Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 9.28 sec).
Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 1.43 sec).
Restored /home/vsts/work/1/s/src/TestUtils/src/TestUtils/TestUtils.csproj (in 6 ms).
Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 18 ms).
Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 60 ms).
Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 29 ms).
Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 37 ms).
Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 71 ms).
1 of 10 projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0/Microsoft.Maui.Maps.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
TestUtils -> /home/vsts/work/1/s/artifacts/bin/TestUtils/Debug/netstandard2.0/Microsoft.Maui.TestUtils.dll
Controls.Core.UnitTests -> /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 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.17] Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.35] Discovered: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.37] Starting: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:02.34] PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.PathGeometry)) [FAIL]
[xUnit.net 00:00:02.37] Path should not be alive!
[xUnit.net 00:00:02.39] Stack Trace:
[xUnit.net 00:00:02.39] /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs(32,0): at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathDataDoesNotLeakWhenGeometryOutlivesIt(Type geometryType)
[xUnit.net 00:00:02.39] --- End of stack trace from previous location ---
Passed PathRenderTransformChangesStillNotifyAfterGc [29 ms]
[xUnit.net 00:00:03.07] PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.RectangleGeometry)) [FAIL]
Failed PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.PathGeometry)) [863 ms]
Error Message:
Path should not be alive!
Stack Trace:
at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathDataDoesNotLeakWhenGeometryOutlivesIt(Type geometryType) in /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs:line 32
--- End of stack trace from previous location ---
[xUnit.net 00:00:03.07] Path should not be alive!
[xUnit.net 00:00:03.08] Stack Trace:
[xUnit.net 00:00:03.08] /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs(32,0): at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathDataDoesNotLeakWhenGeometryOutlivesIt(Type geometryType)
[xUnit.net 00:00:03.08] --- End of stack trace from previous location ---
[xUnit.net 00:00:03.92] PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.EllipseGeometry)) [FAIL]
[xUnit.net 00:00:03.92] Path should not be alive!
[xUnit.net 00:00:03.92] Stack Trace:
[xUnit.net 00:00:03.92] /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs(32,0): at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathDataDoesNotLeakWhenGeometryOutlivesIt(Type geometryType)
[xUnit.net 00:00:03.92] --- End of stack trace from previous location ---
Failed PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.RectangleGeometry)) [748 ms]
Error Message:
Path should not be alive!
Stack Trace:
at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathDataDoesNotLeakWhenGeometryOutlivesIt(Type geometryType) in /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs:line 32
--- End of stack trace from previous location ---
Failed PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.EllipseGeometry)) [846 ms]
Error Message:
Path should not be alive!
Stack Trace:
at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathDataDoesNotLeakWhenGeometryOutlivesIt(Type geometryType) in /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs:line 32
--- End of stack trace from previous location ---
[xUnit.net 00:00:04.66] PathRenderTransformDoesNotLeakWhenTransformOutlivesIt [FAIL]
[xUnit.net 00:00:04.66] Path should not be alive!
[xUnit.net 00:00:04.66] Stack Trace:
[xUnit.net 00:00:04.66] /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs(54,0): at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathRenderTransformDoesNotLeakWhenTransformOutlivesIt()
[xUnit.net 00:00:04.66] --- End of stack trace from previous location ---
[xUnit.net 00:00:04.69] Finished: Microsoft.Maui.Controls.Core.UnitTests
Failed PathRenderTransformDoesNotLeakWhenTransformOutlivesIt [730 ms]
Error Message:
Path should not be alive!
Stack Trace:
at Microsoft.Maui.Controls.Core.UnitTests.PathMemoryTests.PathRenderTransformDoesNotLeakWhenTransformOutlivesIt() in /_/src/Controls/tests/Core.UnitTests/PathMemoryTests.cs:line 54
--- End of stack trace from previous location ---
Passed PathDataReassignmentMovesChangeSubscription [3 ms]
Passed PathRenderTransformReassignmentMovesChangeSubscription [< 1 ms]
Passed PathDataChangesStillNotifyAfterGc [19 ms]
Test Run Failed.
Total tests: 8
Passed: 4
Failed: 4
Total time: 5.1580 Seconds
🟢 With fix — 🧪 PathMemoryTests: PASS ✅ · 82s
Determining projects to restore...
All projects are up-to-date for restore.
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0/Microsoft.Maui.Maps.dll
Controls.BindingSourceGen -> /home/vsts/work/1/s/artifacts/bin/Controls.BindingSourceGen/Debug/netstandard2.0/Microsoft.Maui.Controls.BindingSourceGen.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
##vso[build.updatebuildnumber]10.0.100-ci+azdo.14634860
Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.dll
Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
TestUtils -> /home/vsts/work/1/s/artifacts/bin/TestUtils/Debug/netstandard2.0/Microsoft.Maui.TestUtils.dll
Controls.Core.UnitTests -> /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll
Test run for /home/vsts/work/1/s/artifacts/bin/Controls.Core.UnitTests/Debug/net10.0/Microsoft.Maui.Controls.Core.UnitTests.dll (.NETCoreApp,Version=v10.0)
VSTest version 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.19] Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.91] Discovered: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.93] Starting: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:02.15] Finished: Microsoft.Maui.Controls.Core.UnitTests
Passed PathRenderTransformChangesStillNotifyAfterGc [53 ms]
Passed PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.PathGeometry)) [26 ms]
Passed PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.RectangleGeometry)) [19 ms]
Passed PathDataDoesNotLeakWhenGeometryOutlivesIt(geometryType: typeof(Microsoft.Maui.Controls.Shapes.EllipseGeometry)) [17 ms]
Passed PathRenderTransformDoesNotLeakWhenTransformOutlivesIt [18 ms]
Passed PathDataReassignmentMovesChangeSubscription [2 ms]
Passed PathRenderTransformReassignmentMovesChangeSubscription [< 1 ms]
Passed PathDataChangesStillNotifyAfterGc [19 ms]
Test Run Successful.
Total tests: 8
Passed: 8
Total time: 2.7694 Seconds
📁 Fix files reverted (1 files)
src/Controls/src/Core/Shapes/Path.cs
📱 UI Tests — GraphicsView,Shape
Detected UI test categories: GraphicsView,Shape
✅ Deep UI tests — 78 passed, 0 failed across 2 categories on platform-pool agent (replaces in-process counts above).
🧪 UI Test Execution Results (deep, platform pool)
| Category | Tests | Snapshot diffs |
|---|---|---|
GraphicsView |
43/43 ✓ | — |
Shape |
35/35 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
📋 Pre-Flight — Context & Validation
Issue: #unknown - GitHub metadata unavailable (gh unauthenticated)
PR: #36273 - Local squashed PR branch pr-review-36273
Platforms Affected: android (requested test platform); shared Controls Shapes code affects all platforms using Path
Files Changed: 1 implementation, 1 test
Key Findings
- PR modifies
src/Controls/src/Core/Shapes/Path.csto preventPathfrom being rooted by long-livedGeometry/Transformevent subscriptions. - PR adds
src/Controls/tests/Core.UnitTests/PathMemoryTests.cs, a unit-test suite that verifies the leak fails without the fix and passes with the fix per the provided gate. - GitHub PR description, issue comments, review comments, and required checks could not be fetched because
ghis unauthenticated in this environment; continued using local diff and existing gate artifact.
Code Review Summary
Verdict: NEEDS_DISCUSSION
Confidence: low
Errors: 0 | Warnings: 0 | Suggestions: 0
Key code review findings:
- No concrete code defects found in local review.
- CI/review metadata is undetermined because
ghauthentication is unavailable. - Failure-mode probes covered reassignment,
Pathcollection while source outlives it, andPathGeometrynested invalidation.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #36273 | Use weak event proxy subscriptions from Path to Geometry, PathGeometry, and Transform, preserving change notifications without strongly rooting Path. |
✅ PASSED (Gate) | src/Controls/src/Core/Shapes/Path.cs, src/Controls/tests/Core.UnitTests/PathMemoryTests.cs |
Original PR |
🔬 Code Review — Deep Analysis
Code Review — PR #36273
Independent Assessment
What this changes: Replaces strong Path subscriptions to Geometry.PropertyChanged, PathGeometry.InvalidatePathGeometryRequested, and Transform.PropertyChanged with weak proxy subscriptions in Path.cs, plus regression tests in PathMemoryTests.cs.
Inferred motivation: Prevent long-lived/shared Geometry or Transform instances from keeping Path instances alive.
Reconciliation with PR Narrative
Author claims: Unavailable. gh is unauthenticated and GitHub page fetch failed.
Agreement/disagreement: Available code/tests indicate a memory-leak fix for Path.Data and Path.RenderTransform. No contradiction found from local code.
Prior Review Reconciliation
Could not query the required GitHub review surfaces because gh authentication is unavailable:
- top-level reviews: unavailable
- inline review comments: unavailable
- issue comments: unavailable
No prior ❌ Error findings found in available local context.
Blast Radius Assessment
- Runs for all instances: No; only
Pathinstances withDataorRenderTransformassigned. - Startup impact: No.
- Static/shared state: No new static state.
CI Status
- Required-check result: undetermined
- Classification: tool/auth unavailable
- Action taken: capped confidence at low; no comments posted. Local targeted validation passed in the pre-run gate using
PathMemoryTests.
Findings
No ❌ Error,
Expert reviewer produced inline-findings.json as an empty array.
Failure-Mode Probing
- Reassignment from old to new geometry/transform: old source is unsubscribed before subscribing new source.
Pathcollected while source outlives it: weak proxy no longer rootsPath; proxy self-unsubscribes on later event/finalizer path.PathGeometrynested figure changes:InvalidatePathGeometryRequestedremains wired through the custom weak proxy.
Verdict: NEEDS_DISCUSSION
Confidence: low
Summary: The code review found no concrete defects, and targeted tests pass in the provided gate. However, GitHub metadata, prior reviews, and required CI status could not be verified due unavailable gh authentication, so this cannot be LGTM under the skill rules.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix | Finalizer unsubscribes original strong event subscriptions. | ❌ FAIL | 1 file | Finalizer cannot run while source event delegate roots Path. |
| 2 | try-fix | Local per-Path callbacks capture only WeakReference. | ✅ PASS | 1 file | Works, but duplicates WeakEventProxy behavior and can leave source-held callback delegates until source events fire. Not better than PR. |
| 3 | try-fix | Remove nested Data/RenderTransform subscriptions entirely. | ❌ FAIL | 1 file | Fixes leak class by removing subscriptions, but breaks in-place mutation notifications. |
| PR | PR #36273 | Weak proxy subscriptions from Path to Geometry, PathGeometry, and Transform. | ✅ PASSED (Gate) | 2 files | Original PR; best balance of correctness and codebase consistency. |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| local expert loop | 1 | Yes | Candidate 1 showed cleanup that depends on Path finalization is too late because the event source roots Path. |
| local expert loop | 2 | Yes | Candidate 2 showed weak target capture passes but local duplicate weak-event code is not preferable to existing MAUI WeakEventProxy infrastructure. |
| local expert loop | 3 | No | Candidate 3 showed eliminating subscriptions is too broad because nested Geometry/Transform mutation notifications are required. Remaining viable approaches collapse to weak-event subscription variants, which the PR already implements with existing framework helpers. |
Exhausted: Yes
Selected Fix: PR #36273 — It is the only passing candidate that preserves nested mutation behavior while using the repository's established WeakEventProxy pattern. Candidate #2 also passes, but is not demonstrably better because it duplicates weak-event machinery locally and has less clean source-side delegate cleanup behavior.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the description is accurate and detailed, but the current title uses a nonstandard [leak-fix] prefix instead of the repository's component-focused title format.
Recommended title
Shapes: Fix Path.Data/RenderTransform memory leak
Recommended description
> [!NOTE]
> 🤖 **AI-generated PR.** This fix and its regression tests were produced by an AI-driven memory-leak workflow and verified locally. Please review carefully before merging.
Fixes #35860
### The leak
`Path.Data` and `Path.RenderTransform` subscribed the `Path` to change notifications on the assigned geometry or transform using strong handlers. A shared or long-lived value could therefore retain every `Path` using it and, through the `Path`, its visual tree or page.
### The fix
Subscribe through `WeakGeometryChangedProxy` and `WeakNotifyPropertyChangedProxy`, owned by a private, lazily-created `PathChangeSubscriptions` helper. The helper finalizer tears down the proxies when the owner is collected, preventing shared geometry and transform instances from rooting the `Path` without adding a public finalizer or changing the public API surface.
### Regression tests
`PathMemoryTests` in `src/Controls/tests/Core.UnitTests/PathMemoryTests.cs` covers:
- `PathDataDoesNotLeakWhenGeometryOutlivesIt`
- `PathRenderTransformDoesNotLeakWhenTransformOutlivesIt`
- `PathDataChangesStillNotifyAfterGc`
- `PathRenderTransformChangesStillNotifyAfterGc`
- `PathDataReassignmentMovesChangeSubscription`
- `PathRenderTransformReassignmentMovesChangeSubscription`
**Verified locally against this PR base (`main`), `Controls.Core.UnitTests`:**
| State | Result |
|---|---|
| Without fix | ❌ leak regressions fail |
| With fix | ✅ targeted `PathMemoryTests` pass |
### Scope
Managed cross-platform change for all platforms. No public API additions.
🏁 Report — Final Recommendation
Comparative Analysis — PR #36273
Candidates
| Rank | Candidate | Regression result | Assessment |
|---|---|---|---|
| 1 | pr |
✅ Passed gate | Uses existing WeakEventProxy infrastructure to prevent source-to-Path rooting while preserving nested Geometry, PathGeometry, and Transform mutation notifications. |
| 2 | pr-plus-reviewer |
✅ Same as PR | Expert reviewer found no actionable changes, so this is equivalent to pr with no extra diff. Ranked just below pr because it is a no-op derivative rather than a distinct improvement. |
| 3 | try-fix-2 |
✅ Passed targeted tests | Functionally viable, but duplicates weak-event machinery inside Path, adds more local fields/closures, and leaves cleanup behavior less aligned with existing MAUI patterns than the PR's proxy-based implementation. |
| 4 | try-fix-1 |
❌ Failed regression tests | Relies on a Path finalizer to unsubscribe strong event handlers, but the long-lived event source keeps Path reachable, so the finalizer cannot run and the leak remains. |
| 5 | try-fix-3 |
❌ Failed regression tests | Removes nested subscriptions and avoids the leak class, but breaks required in-place mutation notifications for Data and RenderTransform. |
Winning candidate
Winner: pr
The raw PR fix is the best candidate because it is the only passing, non-duplicative implementation that both fixes the memory leak and preserves existing nested mutation behavior using the repository's established weak-event proxy pattern. try-fix-2 also passes the targeted tests, but it reimplements weak subscription logic locally and is not an improvement over the shared infrastructure already used by the PR. Candidates that failed regression tests are ranked below all passing candidates as required.
Notes on pr-plus-reviewer
The expert reviewer produced no actionable inline findings (inline-findings.json is []), so there was no reviewer feedback to apply in the sandbox copy. pr-plus-reviewer is therefore equivalent to pr and does not supersede it.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
@MauiBot The current-head review reports no errors, warnings, suggestions, inline findings, or concrete code defects. Its low-confidence verdict is only unavailable GitHub metadata and required-CI status, so no no-op code change is warranted. Ready for another review. |
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!
Note
🤖 AI-generated PR. This fix and its regression tests were produced by an AI-driven memory-leak workflow and verified locally. Please review carefully before merging.
Fixes #35860
The leak
Path.DataandPath.RenderTransformsubscribed thePathto change notifications on the assigned geometry or transform using strong handlers. A shared or long-lived value could therefore retain everyPathusing it and, through thePath, its visual tree or page.The fix
Subscribe through
WeakGeometryChangedProxyandWeakNotifyPropertyChangedProxy, owned by a private, lazily-createdPathChangeSubscriptionshelper. The helper finalizer tears down the proxies when the owner is collected, preventing shared geometry and transform instances from rooting thePathwithout adding a public finalizer or changing the public API surface.Regression tests
PathMemoryTestsinsrc/Controls/tests/Core.UnitTests/PathMemoryTests.cscovers:PathDataDoesNotLeakWhenGeometryOutlivesItPathRenderTransformDoesNotLeakWhenTransformOutlivesItPathDataChangesStillNotifyAfterGcPathRenderTransformChangesStillNotifyAfterGcPathDataReassignmentMovesChangeSubscriptionPathRenderTransformReassignmentMovesChangeSubscriptionVerified locally against this PR base (
main),Controls.Core.UnitTests:PathMemoryTestspassScope
Managed cross-platform change for all platforms. No public API additions.