Skip to content

Fix memory leak when clearing shared PathSegments from PathFigure - #35873

Merged
kubaflo merged 3 commits into
dotnet:inflight/currentfrom
devanathan-vaithiyanathan:fix-35796
Jun 19, 2026
Merged

Fix memory leak when clearing shared PathSegments from PathFigure#35873
kubaflo merged 3 commits into
dotnet:inflight/currentfrom
devanathan-vaithiyanathan:fix-35796

Conversation

@devanathan-vaithiyanathan

Copy link
Copy Markdown
Contributor

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!

Issue Details

Memory leak in PathFigure.Segments.Clear() when PathSegment objects are shared. The clear operation raises a reset change, and old segment handlers were not fully unsubscribed, so shared segments could keep old PathFigure instances alive and retain the related Path visual tree and page/viewmodel objects.

Description of Change

Added segment subscription tracking using a _subscribedSegments list to prevent memory leaks. The code now properly unsubscribes from segments when .Clear() is called by handling the Reset collection action. Subscription logic was extracted into reusable methods: SubscribeToPathSegmentPropertyChanged(), UnsubscribeFromPathSegmentPropertyChanged(), and UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared segments from triggering invalidation after removal.

Issues Fixed

Fixes #35796

Tested the behavior in the following platforms.

  • Android
  • Windows
  • iOS
  • Mac
Before After
iOS
Before.mov
iOS
After.mov

@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35873

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35873"

@vishnumenon2684 vishnumenon2684 added partner/syncfusion Issues / PR's with Syncfusion collaboration community ✨ Community Contribution labels Jun 11, 2026
@github-actions github-actions Bot added the area-drawing Shapes, Borders, Shadows, Graphics, BoxView, custom drawing label Jun 11, 2026
@kubaflo

kubaflo commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

/review -b feature/enhanced-reviewer

@github-actions github-actions Bot added the s/agent-review-in-progress AI review is currently running for this PR label Jun 12, 2026

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.


void SubscribeToPathSegmentPropertyChanged(PathSegment pathSegment)
{
if (_subscribedSegments.Contains(pathSegment))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Logic and Correctness — This makes subscriptions unique by segment instance, but PathSegmentCollection is an ObservableCollection<PathSegment> and allows the same segment object to appear multiple times. Scenario: add the same LineSegment twice, then remove one occurrence; UnsubscribeFromPathSegmentPropertyChanged detaches the only handler, so changing the remaining segment no longer invalidates the figure. Please track per-occurrence subscriptions or keep a ref count and only detach when the last occurrence is removed.

@MauiBot MauiBot added s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review) labels Jun 12, 2026
MauiBot

This comment was marked as outdated.

@MauiBot MauiBot removed the s/agent-review-in-progress AI review is currently running for this PR label Jun 12, 2026
@sheiksyedm
sheiksyedm marked this pull request as ready for review June 15, 2026 17:05

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Expert Review — 1 findings

See inline comments for details.


void SubscribeToPathSegmentPropertyChanged(PathSegment pathSegment)
{
if (_subscribedSegments.Contains(pathSegment))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[major] Logic and Correctness - _subscribedSegments.Contains(pathSegment) treats segment subscriptions as a unique set, but PathSegmentCollection inherits ObservableCollection<PathSegment> and allows the same segment instance to appear multiple times. If the same LineSegment is added twice and one occurrence is removed or replaced, UnsubscribeFromPathSegmentPropertyChanged detaches the only handler while another occurrence remains in Segments; subsequent property changes no longer invalidate the path, leaving rendered geometry stale. Track reference counts per PathSegment instead, and add a duplicate-segment regression test.

@MauiBot MauiBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AI Review Summary

@devanathan-vaithiyanathan — new AI review results are available based on this last commit: 01d6358. To request a fresh review after new comments or commits, comment /review rerun.

Gate Passed Code Review In Review Confidence Low Platform Android

Review Sessions — click to expand
Gate — Test Before & After Fix

Gate Result: ✅ PASSED

Platform: ANDROID · Base: main · Merge base: dc08af3b

Test Without Fix (expect FAIL) With Fix (expect PASS)
🧪 PathSegmentTests PathSegmentTests ✅ FAIL — 102s ✅ PASS — 63s
🔴 Without fix — 🧪 PathSegmentTests: FAIL ✅ · 102s
  Determining projects to restore...
  Restored /home/vsts/work/1/s/src/Controls/tests/Core.UnitTests/Controls.Core.UnitTests.csproj (in 5.03 sec).
  Restored /home/vsts/work/1/s/src/TestUtils/src/TestUtils/TestUtils.csproj (in 167 ms).
  Restored /home/vsts/work/1/s/src/Graphics/src/Graphics/Graphics.csproj (in 230 ms).
  Restored /home/vsts/work/1/s/src/Essentials/src/Essentials.csproj (in 968 ms).
  Restored /home/vsts/work/1/s/src/Core/src/Core.csproj (in 108 ms).
  Restored /home/vsts/work/1/s/src/Controls/src/Xaml/Controls.Xaml.csproj (in 8.27 sec).
  Restored /home/vsts/work/1/s/src/Controls/src/Core/Controls.Core.csproj (in 22 ms).
  Restored /home/vsts/work/1/s/src/Core/maps/src/Maps.csproj (in 657 ms).
  Restored /home/vsts/work/1/s/src/Controls/Maps/src/Controls.Maps.csproj (in 585 ms).
  1 of 10 projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Core -> /home/vsts/work/1/s/artifacts/bin/Core/Debug/net10.0/Microsoft.Maui.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  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.90-ci+azdo.14420410
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Controls.Xaml -> /home/vsts/work/1/s/artifacts/bin/Controls.Xaml/Debug/net10.0/Microsoft.Maui.Controls.Xaml.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.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.14]   Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.22]   Discovered:  Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.23]   Starting:    Microsoft.Maui.Controls.Core.UnitTests
  Passed TestBezierSegmentConstructor [8 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144", count: 2) [11 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96", count: 7) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "", count: 0) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48", count: 1) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 9) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 8) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0", count: 4) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96", count: 6) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 10) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150", count: 3) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0", count: 5) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0", count: 4) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0", count: 5) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144", count: 2) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96", count: 7) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96", count: 6) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150", count: 3) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "", count: 0) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 9) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 10) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 8) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48", count: 1) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 10) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0", count: 4) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96", count: 6) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 8) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96", count: 7) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 9) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150", count: 3) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "", count: 0) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144", count: 2) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48", count: 1) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0", count: 5) [< 1 ms]
  Passed TestLineSegmentConstructor [< 1 ms]
  Passed TestArcSegmentConstructor [1 ms]
[xUnit.net 00:00:01.34]       Assert.Equal() Failure: Values differ
[xUnit.net 00:00:01.34]       Expected: 2
[xUnit.net 00:00:01.34]       Actual:   3
[xUnit.net 00:00:01.34]       Stack Trace:
[xUnit.net 00:00:01.35]         /_/src/Controls/tests/Core.UnitTests/PathSegmentTests.cs(147,0): at Microsoft.Maui.Controls.Core.UnitTests.PathSegmentTests.ClearingSegmentsDetachesPropertyChangedFromRemovedSegments()
[xUnit.net 00:00:01.35]            at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
[xUnit.net 00:00:01.35]            at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
[xUnit.net 00:00:01.35]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:01.34]     ClearingSegmentsDetachesPropertyChangedFromRemovedSegments [FAIL]
  Failed ClearingSegmentsDetachesPropertyChangedFromRemovedSegments [4 ms]
  Error Message:
   Assert.Equal() Failure: Values differ
Expected: 2
Actual:   3
  Stack Trace:
     at Microsoft.Maui.Controls.Core.UnitTests.PathSegmentTests.ClearingSegmentsDetachesPropertyChangedFromRemovedSegments() in /_/src/Controls/tests/Core.UnitTests/PathSegmentTests.cs:line 147
   at System.Reflection.MethodBaseInvoker.InterpretedInvoke_Method(Object obj, IntPtr* args)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)
  Passed TestQuadraticBezierSegmentConstructor [< 1 ms]

Test Run Failed.
Total tests: 38
     Passed: 37
     Failed: 1
 Total time: 1.8274 Seconds

🟢 With fix — 🧪 PathSegmentTests: PASS ✅ · 63s
  Determining projects to restore...
  All projects are up-to-date for restore.
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Graphics -> /home/vsts/work/1/s/artifacts/bin/Graphics/Debug/net10.0/Microsoft.Maui.Graphics.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Essentials -> /home/vsts/work/1/s/artifacts/bin/Essentials/Debug/net10.0/Microsoft.Maui.Essentials.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  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.90-ci+azdo.14420410
  Controls.Core -> /home/vsts/work/1/s/artifacts/bin/Controls.Core/Debug/net10.0/Microsoft.Maui.Controls.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Maps -> /home/vsts/work/1/s/artifacts/bin/Maps/Debug/net10.0/Microsoft.Maui.Maps.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  Controls.Maps -> /home/vsts/work/1/s/artifacts/bin/Controls.Maps/Debug/net10.0/Microsoft.Maui.Controls.Maps.dll
  ##vso[build.updatebuildnumber]10.0.90-ci+azdo.14420410
  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.24]   Discovering: Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:02.46]   Discovered:  Microsoft.Maui.Controls.Core.UnitTests
[xUnit.net 00:00:02.47]   Starting:    Microsoft.Maui.Controls.Core.UnitTests
  Passed TestBezierSegmentConstructor [23 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144", count: 2) [23 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96", count: 7) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "", count: 0) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48", count: 1) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 9) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 8) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0", count: 4) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96", count: 6) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 10) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150", count: 3) [< 1 ms]
  Passed TestPolyBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0", count: 5) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0", count: 4) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0", count: 5) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144", count: 2) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96", count: 7) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96", count: 6) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150", count: 3) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "", count: 0) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 9) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 10) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 8) [< 1 ms]
  Passed TestPolyQuadraticBezierSegmentConstructor(points: "0 48", count: 1) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 10) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0", count: 4) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96", count: 6) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 8) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96", count: 7) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0, 192 96, 50 96, "···, count: 9) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150", count: 3) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "", count: 0) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144", count: 2) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48", count: 1) [< 1 ms]
  Passed TestPolyLineSegmentConstructor(points: "0 48, 0 144, 96 150, 100 0, 192 0", count: 5) [< 1 ms]
  Passed TestLineSegmentConstructor [1 ms]
  Passed TestArcSegmentConstructor [4 ms]
[xUnit.net 00:00:02.64]   Finished:    Microsoft.Maui.Controls.Core.UnitTests
  Passed ClearingSegmentsDetachesPropertyChangedFromRemovedSegments [6 ms]
  Passed TestQuadraticBezierSegmentConstructor [5 ms]

Test Run Successful.
Total tests: 38
     Passed: 38
 Total time: 3.7743 Seconds

📁 Fix files reverted (1 files)
  • src/Controls/src/Core/Shapes/PathFigure.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: #35796 - PathFigure.Segments.Clear() leaks Path visual trees when removed segments are shared
PR: #35873 - Fix memory leak when clearing shared PathSegments from PathFigure
Platforms Affected: Android, iOS (platform-neutral Shapes core code)
Files Changed: 1 implementation, 1 test

Key Findings

  • PathSegmentCollection.Clear() raises Reset without OldItems, so the old implementation leaves removed segment PropertyChanged subscriptions attached.
  • PR fix handles Reset/Clear but tracks subscriptions as unique segment instances, which breaks duplicate entries of the same PathSegment in one collection.
  • Existing inline review from MauiBot reports the duplicate-entry regression; it remains present in the current PR diff.

Code Review Summary

Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 0 | Suggestions: 0

Key code review findings:

  • src/Controls/src/Core/Shapes/PathFigure.cs:163-180 treats duplicate PathSegment instances as a single subscription; removing one duplicate detaches the handler while another occurrence remains.

Fix Candidates

# Source Approach Test Result Files Changed Notes
PR PR #35873 Track unique subscribed segments in a list and handle Reset by detaching all tracked items then rebuilding from the sender collection. ✅ PASSED (Gate) PathFigure.cs, PathSegmentTests.cs Original PR; gate passed, but duplicate segment regression remains.

Code Review — Deep Analysis

Code Review — PR #35873

Independent Assessment

What this changes: PathFigure now tracks subscribed PathSegment instances, handles NotifyCollectionChangedAction.Reset, and detaches segment PropertyChanged handlers when Segments.Clear() removes items.
Inferred motivation: Avoid stale event subscriptions retaining removed/shared segments and causing invalidation after removal.

Reconciliation with PR Narrative

Author claims: Fixes a memory leak from PathFigure.Segments.Clear() when shared PathSegment objects remain alive.
Agreement/disagreement: The reset/clear leak scenario is addressed, but the new unique-instance subscription tracking regresses duplicate-segment semantics.

Prior Review Reconciliation

Prior ❌ Error Finding Source Status Evidence
Duplicate segment instances can lose invalidation because _subscribedSegments is treated as unique. MauiBot inline review ❌ Unresolved Current PathFigure.cs:163-180 still uses Contains, subscribes once, and unsubscribes on first removed occurrence.

Blast Radius Assessment

  • Runs for all instances: Yes — every PathFigure now uses the subscription list.
  • Startup impact: No.
  • Static/shared state: No static state, but per-instance event subscription state affects rendering invalidation.

CI Status

  • Required-check result: gh pr checks --required unavailable due missing GitHub auth.
  • API fallback: check status could not be fully verified in this environment.
  • Classification: undetermined.
  • Action taken: confidence capped low.

Findings

❌ Error — Duplicate segment instances can lose invalidation

PathSegmentCollection is an unrestricted ObservableCollection<PathSegment>, so the same segment object can appear more than once. The new logic treats _subscribedSegments as a unique set at src/Controls/src/Core/Shapes/PathFigure.cs:163; removing one duplicate then detaches the only handler at lines 179-180 even if another occurrence remains in Segments. Subsequent mutations of the remaining segment no longer invalidate the figure. Use reference counting, or only unsubscribe when the segment no longer exists in the collection.

Failure-Mode Probing

  • Duplicate segment added twice, one occurrence removed: invalidation is lost incorrectly.
  • Segments.Clear(): removed segments are detached correctly.
  • Segments replaced/null: previous collection and tracked segments are detached.
  • Shared segment across different figures: per-figure handlers still work unless duplicate references exist within one figure.

Verdict: NEEDS_CHANGES

Confidence: low (code issue confidence is high, but CI status is unverifiable)
Summary: The intended leak fix is directionally sound, but the implementation introduces a concrete correctness regression for duplicate segment references. This should be fixed before merge.


Fix — Analysis & Comparison

Fix Candidates

# Source Approach Test Result Files Changed Notes
1 try-fix-1 Reference-count PathSegment subscriptions with Dictionary<PathSegment, int>; detach only when the final occurrence is removed, and rebuild counts on Reset/Clear. ✅ PASS PathFigure.cs; candidate-only duplicate tests in PathSegmentTests.cs Passes gate-scoped PathSegment tests and duplicate probes. Demonstrably better than PR because PR fails RemovingOneDuplicateSegmentKeepsRemainingSegmentSubscribed.
PR PR #35873 Track unique subscribed segments in a list and handle Reset by detaching all tracked items then rebuilding from sender collection. ✅ PASSED (Gate); ❌ fails duplicate probe PathFigure.cs, PathSegmentTests.cs Original PR fixes Clear/Reset leak but mishandles duplicate segment occurrences.

Cross-Pollination

Model Round New Ideas? Details
maui-expert-reviewer / gpt-5.5 1 Yes Proposed reference-counted subscription tracking to preserve duplicate collection multiplicity.

Exhausted: No — stopped per user criterion because candidate 1 passed all scoped tests and is demonstrably better than the PR fix.
Selected Fix: Candidate #1 — It preserves the PR's Clear/Reset leak fix and also preserves duplicate PathSegment semantics that the PR regresses.


Report — Final Recommendation

Comparative Analysis - PR #35873

Candidate ranking

Rank Candidate Regression result Assessment
1 pr-plus-reviewer Passed by construction from the reviewed reference-count fix; equivalent to try-fix-1 Best path: keeps the PR's Clear/Reset leak fix, applies the expert reviewer's reference-counted subscription feedback, and adds duplicate-segment coverage.
2 try-fix-1 PASS Technically sound and empirically validated with the scoped PathSegmentTests plus duplicate probes. It is effectively the same implementation as pr-plus-reviewer, but as an external try-fix candidate rather than the PR fix with reviewer feedback applied.
3 pr Gate PASS; duplicate probe FAIL Fixes the original Clear/Reset leak, but failed the duplicate-occurrence regression probe and has an unresolved major expert-review finding. Per the ranking rule, this failed-regression candidate ranks below the passing candidates.

Candidate details

pr

The raw PR tracks subscribed segments in a unique List<PathSegment>. That handles Clear()/Reset where OldItems is unavailable, but it treats duplicate occurrences of the same segment object as one subscription. Removing one duplicate occurrence detaches the only handler while another occurrence remains, so later mutations no longer invalidate the path.

pr-plus-reviewer

The expert reviewer feedback changes subscription tracking to reference counts by PathSegment. The handler is attached once for the first occurrence, duplicate additions increment the count, removals decrement the count, and the handler is detached only after the final occurrence is removed. This preserves the PR's leak fix and covers the duplicate semantics gap identified by review.

try-fix-1

This candidate independently implemented the same reference-counted approach with Dictionary<PathSegment, int>. It passed the scoped PathSegment tests and duplicate probes, and its comparison run showed the raw PR fails RemovingOneDuplicateSegmentKeepsRemainingSegmentSubscribed.

Winner

Winner: pr-plus-reviewer. It is the best candidate because it keeps the PR-owned fix path while incorporating the expert reviewer's actionable correction. try-fix-1 is equally strong technically, but pr-plus-reviewer is the reviewed PR fix plus the same improvement, so it is the preferred final candidate.


Future Action — review latest findings

No alternative fix was selected for this run. Review the session findings and CI results before merging.

@kubaflo
kubaflo changed the base branch from main to inflight/current June 19, 2026 12:58
@kubaflo
kubaflo merged commit 15e0ec5 into dotnet:inflight/current Jun 19, 2026
29 of 38 checks passed
@github-actions github-actions Bot added this to the .NET 10 SR9 milestone Jun 19, 2026
PureWeen pushed a commit that referenced this pull request Jun 22, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
kubaflo pushed a commit that referenced this pull request Jun 25, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
kubaflo pushed a commit that referenced this pull request Jul 3, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
@kubaflo kubaflo mentioned this pull request Jul 6, 2026
kubaflo pushed a commit that referenced this pull request Jul 6, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
PureWeen pushed a commit that referenced this pull request Jul 7, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
kubaflo pushed a commit that referenced this pull request Jul 10, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
kubaflo pushed a commit that referenced this pull request Jul 15, 2026
…5873)

<!-- Please let the below note in for people that find this PR -->
> [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from
this PR and let us know in a comment if this change resolves your issue.
Thank you!

<!--
!!!!!!! MAIN IS THE ONLY ACTIVE BRANCH. MAKE SURE THIS PR IS TARGETING
MAIN. !!!!!!!
-->
### Issue Details
Memory leak in PathFigure.Segments.Clear() when PathSegment objects are
shared. The clear operation raises a reset change, and old segment
handlers were not fully unsubscribed, so shared segments could keep old
PathFigure instances alive and retain the related Path visual tree and
page/viewmodel objects.

### Description of Change

<!-- Enter description of the fix in this section -->
Added segment subscription tracking using a _subscribedSegments list to
prevent memory leaks. The code now properly unsubscribes from segments
when .Clear() is called by handling the Reset collection action.
Subscription logic was extracted into reusable methods:
SubscribeToPathSegmentPropertyChanged(),
UnsubscribeFromPathSegmentPropertyChanged(), and
UnsubscribeFromAllPathSegmentPropertyChanged(). This prevents cleared
segments from triggering invalidation after removal.

### Issues Fixed

<!-- Please make sure that there is a bug logged for the issue being
fixed. The bug should describe the problem and how to reproduce it. -->

Fixes #35796 

<!--
Are you targeting main? All PRs should target the main branch unless
otherwise noted.
-->

**Tested the behavior in the following platforms.**
- [x] Android
- [x] Windows
- [x] iOS
- [x] Mac

| Before  | After  |
|---------|--------|
| **iOS**<br> <video
src="https://github.com/user-attachments/assets/9e3fad2a-aeb9-4159-86f8-27dbb9d4d0fb"
width="300" height="600"> | **iOS**<br> <video
src="https://github.com/user-attachments/assets/5dfcf942-fa2b-49b5-9e45-590850af84d0"
width="300" height="600"> |
kubaflo pushed a commit that referenced this pull request Jul 16, 2026
Rebased onto inflight/current, which already contains #35873's shared-PathSegment
Clear() fix using strong CollectionChanged/PropertyChanged subscriptions — the
exact mechanism #36377 reports (a shared/long-lived PathSegmentCollection roots
the PathFigure). Converts those subscriptions to WeakNotifyCollectionChangedProxy /
WeakNotifyPropertyChangedProxy, preserving the existing teardown behavior guarded
by the PathFigure/PathSegment unit tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e27685d0-fe80-460a-aa05-83d2ab9bf032
kubaflo pushed a commit that referenced this pull request Jul 16, 2026
Rebased onto inflight/current, which already contains #35873's shared-PathSegment
Clear() fix using strong CollectionChanged/PropertyChanged subscriptions — the
exact mechanism #36377 reports (a shared/long-lived PathSegmentCollection roots
the PathFigure). Converts those subscriptions to WeakNotifyCollectionChangedProxy /
WeakNotifyPropertyChangedProxy, preserving the existing teardown behavior guarded
by the PathFigure/PathSegment unit tests. Adds the required PublicAPI entry for
the new ~PathFigure() finalizer (which unsubscribes the weak proxies).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e27685d0-fe80-460a-aa05-83d2ab9bf032
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 20, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-drawing Shapes, Borders, Shadows, Graphics, BoxView, custom drawing community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration s/agent-fix-win AI found a better alternative fix than the PR s/agent-reviewed PR was reviewed by AI agent workflow (full 4-phase review)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PathFigure.Segments.Clear() leaks Path visual trees when removed segments are shared

5 participants