Description
When multiple Border controls use the same DoubleCollection from Application.Resources for StrokeDashArray, the platform dash-pattern path subscribes to that collection and does not detach when the Border is discarded. Because the app-level DoubleCollection is long-lived, it retains every realized dashed Border.
In realistic item templates, a retained Border can also retain the page through normal page-level event handlers, which then keeps the CollectionView, page view model, item view models, and associated payloads alive.
The attached repro compares three scenarios: solid-border control, shared AppResource dash array, and per-border dash-array mitigation. The control and mitigation release realized Borders; the shared AppResource scenario retains all realized dashed Borders and all pages. Current device proofs show roughly 180 MB of retained managed payload after pushing and popping 20 pages.
Steps to Reproduce
-
Clone the repro branch:
git clone https://github.com/AdamEssenmacher/maui.git
cd maui
git checkout repro/border-dash-array-leak
-
Build the sample:
dotnet build Microsoft.Maui.BuildTasks.slnf
dotnet build src/Controls/samples/BorderDashArrayLeakRepro/BorderDashArrayLeakRepro.csproj -f net10.0-android
dotnet build src/Controls/samples/BorderDashArrayLeakRepro/BorderDashArrayLeakRepro.csproj -f net10.0-ios -p:RuntimeIdentifier=iossimulator-arm64
-
Run Android proof:
adb install --no-incremental -r artifacts/bin/BorderDashArrayLeakRepro/Debug/net10.0-android/android-arm64/com.microsoft.maui.borderdasharrayleakrepro-Signed.apk
adb shell am start -S \
-n com.microsoft.maui.borderdasharrayleakrepro/com.microsoft.maui.borderdasharrayleakrepro.MainActivity \
--ez BORDER_DASH_PROOF true
adb shell run-as com.microsoft.maui.borderdasharrayleakrepro cat files/border-dash-array-device-proof.txt
-
Run iOS simulator proof:
xcrun simctl install booted artifacts/bin/BorderDashArrayLeakRepro/Debug/net10.0-ios/iossimulator-arm64/BorderDashArrayLeakRepro.app
SIMCTL_CHILD_BORDER_DASH_PROOF=1 \
xcrun simctl launch --terminate-running-process booted com.microsoft.maui.borderdasharrayleakrepro
find "$(xcrun simctl get_app_container booted com.microsoft.maui.borderdasharrayleakrepro data)" \
-name border-dash-array-device-proof.txt -print -exec cat {} \;
What The Repro Does
The automated proof runs three scenarios: solid-border control, shared Application.Resources DoubleCollection assigned to Border.StrokeDashArray, and a per-border dash-array mitigation. Each scenario pushes and pops 20 realistic CollectionView pages, forces full GC, and reports surviving weak references.
Observed
Control and mitigation release realized Borders. The shared AppResource dash-array case retains all realized dashed Borders and, through normal page-level tap handlers, retains the pages, CollectionViews, and view-model payloads.
| Platform |
Control |
Shared AppResource dash array |
Per-border mitigation |
Android 16 (sdk_gphone16k_arm64) |
1/20 pages, 0/280 Borders, 9.0 MB payload |
20/20 pages, 280/280 Borders, 180.0 MB payload |
1/20 pages, 0/280 Borders, 9.0 MB payload |
iOS 26.4 (iPhone 17e simulator) |
0/20 pages, 0/300 Borders, 0 B payload |
20/20 pages, 300/300 Borders, 180.0 MB payload |
0/20 pages, 0/300 Borders, 0 B payload |
The Android control/mitigation runs leave one page behind as platform cleanup noise, but the shared AppResource scenario retains an additional 19 pages, all realized dashed Borders, and about 171 MB more payload than the control on that run. On iOS, control/mitigation retain no pages or Borders, while the shared AppResource scenario retains all 20 pages and 180 MB of payload.
Link to public reproduction project repository
Repro branch: AdamEssenmacher/maui/tree/repro/border-dash-array-leak
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, Android, macOS, I was not able test on other platforms
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output
Description
When multiple
Bordercontrols use the sameDoubleCollectionfromApplication.ResourcesforStrokeDashArray, the platform dash-pattern path subscribes to that collection and does not detach when theBorderis discarded. Because the app-levelDoubleCollectionis long-lived, it retains every realized dashedBorder.In realistic item templates, a retained
Bordercan also retain the page through normal page-level event handlers, which then keeps theCollectionView, page view model, item view models, and associated payloads alive.The attached repro compares three scenarios: solid-border control, shared AppResource dash array, and per-border dash-array mitigation. The control and mitigation release realized Borders; the shared AppResource scenario retains all realized dashed Borders and all pages. Current device proofs show roughly
180 MBof retained managed payload after pushing and popping 20 pages.Steps to Reproduce
Clone the repro branch:
git clone https://github.com/AdamEssenmacher/maui.git cd maui git checkout repro/border-dash-array-leakBuild the sample:
Run Android proof:
adb install --no-incremental -r artifacts/bin/BorderDashArrayLeakRepro/Debug/net10.0-android/android-arm64/com.microsoft.maui.borderdasharrayleakrepro-Signed.apk adb shell am start -S \ -n com.microsoft.maui.borderdasharrayleakrepro/com.microsoft.maui.borderdasharrayleakrepro.MainActivity \ --ez BORDER_DASH_PROOF true adb shell run-as com.microsoft.maui.borderdasharrayleakrepro cat files/border-dash-array-device-proof.txtRun iOS simulator proof:
What The Repro Does
The automated proof runs three scenarios: solid-border control, shared
Application.ResourcesDoubleCollectionassigned toBorder.StrokeDashArray, and a per-border dash-array mitigation. Each scenario pushes and pops 20 realisticCollectionViewpages, forces full GC, and reports surviving weak references.Observed
Control and mitigation release realized
Borders. The shared AppResource dash-array case retains all realized dashedBorders and, through normal page-level tap handlers, retains the pages,CollectionViews, and view-model payloads.sdk_gphone16k_arm64)iPhone 17esimulator)The Android control/mitigation runs leave one page behind as platform cleanup noise, but the shared AppResource scenario retains an additional 19 pages, all realized dashed Borders, and about 171 MB more payload than the control on that run. On iOS, control/mitigation retain no pages or Borders, while the shared AppResource scenario retains all 20 pages and 180 MB of payload.
Link to public reproduction project repository
Repro branch: AdamEssenmacher/maui/tree/repro/border-dash-array-leak
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, Android, macOS, I was not able test on other platforms
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output