Skip to content

Border.StrokeDashArray leaks dashed Borders when using a shared Application resource #35492

Description

@AdamEssenmacher

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

  1. Clone the repro branch:

    git clone https://github.com/AdamEssenmacher/maui.git
    cd maui
    git checkout repro/border-dash-array-leak
  2. 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
  3. 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
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions