[iOS & Mac] Fix and Re-enable "Handler Does Not Leak" Device Tests for Stepper control#36741
[iOS & Mac] Fix and Re-enable "Handler Does Not Leak" Device Tests for Stepper control#36741BagavathiPerumal wants to merge 2 commits into
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36741Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36741" |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run maui-pr-uitests , maui-pr-devicetests |
|
Azure Pipelines: Successfully started running 2 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
MauiBot
left a comment
There was a problem hiding this comment.
AI Review Summary
@BagavathiPerumal — new AI review results are available based on this last commit:
ad6607a.
🗂️ Review Sessions — click to expand
🚦 Gate — Test Before & After Fix
Gate Result: ❌ FAILED
Platform: IOS · Base: main · Merge base: ee21c935
🩺 Fix does not pass the tests — every test still fails after applying the fix. The PR's change does not resolve the failure(s).
| Test | Without Fix (expect FAIL) | With Fix (expect PASS) |
|---|---|---|
📱 MemoryTests MemoryTests |
✅ FAIL — 7516s | ❌ FAIL — 3376s |
🔴 Without fix — 📱 MemoryTests: FAIL ✅ · 7516s
Error-relevant lines (filtered from the build log):
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:39.9186380] 2026-07-23 17:21:39.882890-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:43.8557450] 2026-07-23 17:21:43.855396-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.3217080] 2026-07-23 17:21:48.321394-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.3604980] 2026-07-23 17:21:48.360176-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:21:48.4330710] 2026-07-23 17:21:48.432701-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:18.4045050] 2026-07-23 17:22:18.365248-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:18.4393710] 2026-07-23 17:22:18.404678-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [17:22:48.3121690] 2026-07-23 17:22:48.311666-0700 Microsoft.Maui.Controls.DeviceTests[6667:45155] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x158ccf240 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
🟢 With fix — 📱 MemoryTests: FAIL ❌ · 3376s
Error-relevant lines (filtered from the build log):
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:18.6797070] 2026-07-23 18:42:18.679361-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:18.7165390] 2026-07-23 18:42:18.716210-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1101190] 2026-07-23 18:42:21.109817-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1466670] 2026-07-23 18:42:21.146285-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:21.1814430] 2026-07-23 18:42:21.181053-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:51.2342890] 2026-07-23 18:42:51.233644-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:42:51.2344020] 2026-07-23 18:42:51.234053-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
�[40m�[37mdbug�[39m�[22m�[49m: [18:43:21.2087820] 2026-07-23 18:43:21.208270-0700 Microsoft.Maui.Controls.DeviceTests[10300:113362] [general] Failed to terminate process: Error Domain=com.apple.extensionKit.errorDomain Code=18 "(null)" UserInfo={NSUnderlyingError=0x31cd40ba0 {Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.terminateprocess, NSLocalizedFailureReason=Client not entitled, RBSPermanent=true}}}
⚠️ Failure Details
- ❌ MemoryTests FAILED with fix (should pass)
Device tests: 2 of 1454 failed
📁 Fix files reverted (2 files)
src/Core/src/Handlers/Stepper/StepperHandler.iOS.cssrc/Core/src/Platform/iOS/StepperExtensions.cs
📱 UI Tests — ViewBaseTests
Detected UI test categories: ViewBaseTests
✅ Deep UI tests — 112 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 |
|---|---|---|
ViewBaseTests |
112/112 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
📋 Pre-Flight — Context & Validation
Issue: #35985 - [Testing] Re-enable Cells and Handler Memory Leak Device Tests for June 08 Candidate Branch
PR: #36741 - [iOS & Mac] Fix and Re-enable "Handler Does Not Leak" Device Tests for Stepper control
Platforms Affected: iOS, MacCatalyst
Files Changed: 2 implementation, 1 test
Key Findings
- PR targets Stepper memory leaks on iOS/macOS 26 after device memory leak validation began catching real leaks exposed by #35487.
- Implementation changes
StepperHandler.iOS.csdisconnect cleanup andStepperExtensions.csiOS 26 flow-direction/subview handling; test change re-enablesHandlerDoesNotLeak(typeof(Stepper)). - No PR inline review comments were present. PR issue comments only requested CI/test runs and review.
- Gate result was provided as skipped because no tests were detected by the prior gate phase; this pre-flight did not rerun gate verification.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 0 | Suggestions: 0
Key code review findings:
- ✗
src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs:81—_platformViewDisposedis a sticky handler-level boolean; after a handler reconnects with a freshUIStepper, a second disconnect returns before_proxy.Disconnect(platformView)and iOS 26 cleanup/disposal.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #36741 | Dispose iOS/MacCatalyst 26 UIStepper on disconnect, avoid iOS 26 subview realization unless RTL mirroring is required, and re-enable Stepper handler leak test. |
src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs, src/Core/src/Platform/iOS/StepperExtensions.cs, src/Controls/tests/DeviceTests/Memory/MemoryTests.cs |
Original PR; code review found reconnect lifecycle issue. |
🔬 Code Review — Deep Analysis
Code Review — PR #36741
Independent Assessment
What this changes: Re-enables Stepper handler leak coverage and changes iOS/MacCatalyst Stepper teardown/flow-direction behavior to avoid iOS 26 Liquid Glass native retains.
Inferred motivation: Fix Stepper memory leaks exposed by device memory tests on iOS/MacCatalyst 26.
Reconciliation with PR Narrative
Author claims: iOS/Mac 26 Stepper has private native retain; dispose on disconnect and avoid unnecessary subview realization.
Agreement/disagreement: Mostly matches, but the reconnect lifecycle path breaks the proposed disposal guard.
Prior Review Reconciliation
No prior ❌ Error findings found.
Blast Radius Assessment
- Runs for all instances: Yes, all iOS/MacCatalyst Stepper handlers.
- Startup impact: No; disconnect/flow-direction paths only.
- Static/shared state: Yes, a
ConditionalWeakTablescoped perUIStepper.
CI Status
- Required-check result: undetermined via
gh(gh auth loginrequired); public check-runs show failures. - Classification: undetermined / red.
- Action taken: invoked
azdo-build-investigator; confidence capped low.
Findings
❌ Error — Reconnected Stepper handlers skip cleanup after first disconnect
src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs:81
_platformViewDisposed is set after the first iOS/MacCatalyst 26 disconnect and is never reset. The same handler instance can later reconnect with a fresh platform view because ElementHandler.SetVirtualView() creates a new platform element when PlatformView is null. On the next disconnect, this early return skips _proxy.Disconnect(platformView) and the iOS 26 disposal workaround for the new UIStepper, reintroducing the leak and leaving the event hookup uncleared. Reset the flag when a new platform view is created/connected, or key the guard to the actual disposed platform view instance.
Failure-Mode Probing
- Handler disconnect/reconnect: broken; the second platform view is not disconnected/disposed.
- Non-iOS 26 platforms: flag is never set; existing behavior is mostly unchanged.
- LTR-only steppers: subviews are not realized, which supports the leak fix.
- RTL then LTR: CWT path appears to reset previously mirrored subviews.
Verdict: NEEDS_CHANGES
Confidence: low, capped by unavailable required-check query/red public checks; code finding itself is high-confidence.
Summary: The main leak fix is vulnerable to MAUI’s normal reconnect lifecycle. Please reset or instance-scope the disposal guard before merge.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix-1 | Instance-scope the disposed-platform-view guard using a weak reference and ReferenceEquals, instead of a sticky handler-level boolean. |
❌ FAILED | 1 file | Fixed the reconnect guard concern, but Stepper still leaked on iOS 26.5. |
| 2 | try-fix-2 | Avoid platformView.Subviews during iOS 26 disconnect cleanup to prevent lazy Liquid Glass subview realization. |
1 file | Local iOS run hit AOT/native aborts and stale failed-test metadata; no reliable fresh xUnit XML. | |
| 3 | try-fix-3 | Use a UIStepper subclass to defer disposal until UIKit detaches the view, avoiding all disconnect-time graph/layer cleanup. |
1 file | Built after a scoping fix, but test produced no xUnit results and captured a crash report. | |
| 4 | try-fix-4 | Prevention-only flow-direction change: do not set iOS 26 Stepper semantic/transform state for untouched identity/default flow direction. | 2 files | Built with nullable warning, but test produced no xUnit results and captured a crash report. | |
| PR | PR #36741 | Dispose iOS/MacCatalyst 26 UIStepper on disconnect and avoid iOS 26 subview realization unless RTL mirroring is required. |
3 files | Original PR; code review found reconnect lifecycle issue. |
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| maui-expert-reviewer | 1 | Yes | Candidate 1: key the double-disconnect guard to the exact UIStepper instance. |
| maui-expert-reviewer | 2 | Yes | Candidate 2: remove the disconnect-time subview walk because reading UIStepper.Subviews may be the remaining leak trigger. |
| maui-expert-reviewer | 3 | Yes | Candidate 3: defer native disposal until UIKit detaches a custom UIStepper subclass. |
| maui-expert-reviewer | 4 | Yes | Candidate 4: avoid touching SemanticContentAttribute/transform for virgin identity/default iOS 26 steppers. |
| maui-expert-reviewer | 5 | No | After candidates covered guard scoping, disconnect subview avoidance, deferred disposal, and prevention-only flow-direction mapping, remaining variants were either trivial combinations or riskier UIKit lifecycle hacks without a clear new hypothesis. |
Exhausted: Yes
Selected Fix: PR #36741 with required changes — none of the alternatives passed all tests. The strongest actionable improvement is to fix the PR's reconnect lifecycle bug by replacing or resetting the sticky _platformViewDisposed guard, but candidate 1 alone did not fix the iOS 26 Stepper leak.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the current title is specific and worth keeping, but the description has a stale test-change claim that is not present in the actual diff.
Recommended title
[iOS & Mac] Stepper: Fix and re-enable Handler Does Not Leak device test
Recommended description
### Issue Details
The device memory leak validation tests began failing in the Candidate branch after the test-related changes introduced in PR https://github.com/dotnet/maui/pull/35487, which exposed actual memory leaks that were previously undetected. The regressions affected the Cells Do Not Leak and Handler Does Not Leak tests on Windows, the Cells Do Not Leak test on iOS, the Handler Does Not Leak test on iOS 26.0, and the Handler Does Not Leak test on macOS 26.0, indicating platform-specific memory leak issues that require fixes.
### Root cause
The Stepper-specific issue occurs because of iOS 26's new "Liquid Glass" rendering, under which the native `UIStepper` can hold an internal, private native retain that is not reachable or releasable through public API. Diagnostics showed the retain count unchanged even after removing the view from its hierarchy and clearing animations.
This was specific to iOS/MacCatalyst 26 only and did not occur on earlier OS versions. A contributing factor was that subview realization and animation were being forced unconditionally, even for the common case where no directional mirroring was needed, adding an extra, avoidable native retain.
### Solution description
The fix explicitly disposes the native `UIStepper` on disconnect, but only for iOS/MacCatalyst 26+ and with a guard against repeating disposal for the same already-disposed native instance. This forces the interop layer to release its native handle immediately instead of waiting on the garbage collector.
It also skips the iOS 26 subview realization and transform/animation work unless directional mirroring is actually required, while still correctly restoring subviews that were previously mirrored if the layout reverts. The Stepper `Handler Does Not Leak` device test is re-enabled by removing the `TESTS_FAILS_ON_IOS && TESTS_FAILS_ON_MACCATALYST` exclusion around `InlineData(typeof(Stepper))`.
### Key files
- `src/Core/src/Handlers/Stepper/StepperHandler.iOS.cs` - disconnect cleanup/disposal for iOS/MacCatalyst 26 `UIStepper`.
- `src/Core/src/Platform/iOS/StepperExtensions.cs` - iOS 26 flow-direction handling that avoids unnecessary `UIStepper.Subviews` realization.
- `src/Controls/tests/DeviceTests/Memory/MemoryTests.cs` - re-enables `HandlerDoesNotLeak(typeof(Stepper))`.
### Platforms validated
- [x] iOS
- [x] Mac
- [ ] Android
- [ ] Windows
### Regression PR
PR https://github.com/dotnet/maui/pull/35487
### Issues Fixed
Fixes https://github.com/dotnet/maui/issues/35985
### Output ScreenShot
|Platform|Before|After|
|--|--|--|
|Mac|<video src="https://github.com/user-attachments/assets/b5c05b33-1d68-4834-8c04-51ddfa7e687f"> |<video src="https://github.com/user-attachments/assets/de4d7267-314c-49a9-b105-7d4929a4e1f9">|
|iOS|<video src="https://github.com/user-attachments/assets/eac0b2d9-b3e4-475c-92cd-48b6833396cc">|<video src="https://github.com/user-attachments/assets/0ee28ad0-2cf3-4759-9506-9a254ad27e0e">|
🏁 Report — Final Recommendation
Comparative report - PR #36741
Candidate ranking
| Rank | Candidate | Result evidence | Assessment |
|---|---|---|---|
| 1 | pr-plus-reviewer |
Not independently validated in this phase; equivalent lifecycle-guard idea failed the Stepper leak test in try-fix-1 |
Best code path among available candidates because it keeps the PR's targeted iOS/MacCatalyst 26 fix and applies the expert reviewer's required reconnect/disconnect lifecycle correction. Still needs reliable iOS regression validation. |
| 2 | pr |
Gate skipped; expert review found a major lifecycle issue | Keeps the author's targeted fix, but the sticky _platformViewDisposed guard can skip _proxy.Disconnect(platformView) and iOS 26 cleanup/disposal for a fresh platform view after handler reuse. |
| 3 | try-fix-2 |
Blocked/inconclusive: local iOS runs produced AOT/native aborts, stale metadata, and no reliable fresh xUnit XML | Plausible technical direction because it combines the instance-scoped guard with removal of the disconnect-time Subviews walk, but it has no trustworthy passing test evidence. |
| 4 | try-fix-3 |
Blocked/invalid: build eventually succeeded, but no xUnit results were produced and a crash report was captured | Riskier UIKit lifecycle approach using a custom UIStepper subclass and deferred disposal. The crash/no-results behavior makes it less suitable than the localized PR path. |
| 5 | try-fix-4 |
Blocked/invalid: no xUnit results, crash report captured, and introduced nullable warning CS8602 |
Distinct prevention-only hypothesis, but it removes the PR's disconnect disposal, lacks validation, and introduces a warning. |
| 6 | try-fix-1 |
Failed iOS 26.5 memory regression test | Correctly fixes the reconnect guard issue, but Handler Does Not Leak(type: typeof(Microsoft.Maui.Controls.Stepper)) still failed. Per ranking rule, a candidate with a failed regression test ranks below candidates without passing evidence only when no passing candidate exists; it cannot outrank the PR-based candidate selected for code correctness. |
No candidate has a clean, reliable passing iOS regression result. The gate result supplied for this run was skipped because no tests were detected, and the try-fix runs either failed the Stepper leak test or produced invalid/no-result runs.
Why pr-plus-reviewer wins
The raw PR is directionally correct and localized: it avoids unnecessary iOS 26 UIStepper.Subviews realization for the normal LTR flow-direction path, explicitly tears down iOS/MacCatalyst 26 native UIStepper instances, and re-enables the Stepper handler leak test. The expert review found one concrete defect in that implementation: _platformViewDisposed is handler-scoped rather than platform-view-scoped.
pr-plus-reviewer applies the smallest safe correction for that defect by tracking the disposed UIStepper instance instead of using a sticky handler boolean. This preserves the double-disconnect guard without suppressing cleanup for fresh platform views after reconnect.
The strongest competing direction is try-fix-2, which also avoids the disconnect-time Subviews walk. That idea remains plausible because try-fix-1 still failed after only fixing the guard, but try-fix-2 did not produce trustworthy test results. try-fix-3 and try-fix-4 are less attractive because they introduce broader lifecycle behavior or warnings and also lack valid test evidence.
Required follow-up before merge
The winning candidate should be treated as "best available candidate", not as a fully validated fix. Because the only reliable try-fix regression result was a failure for the same guard-only improvement, the PR should rerun the iOS Stepper memory device test after applying the reviewer feedback, and the author should investigate whether the disconnect-time platformView.Subviews cleanup still forces the iOS 26 Liquid Glass retain.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
MauiBot
left a comment
There was a problem hiding this comment.
Expert Review — 1 findings
See inline comments for details.
…and disconnect the handler before verifying collection in the memory leak test.
I have reset _platformViewDisposed = false in ConnectHandler so the guard scopes to the current UIStepper instead of leaking across reconnects. Updated the field comment accordingly and addressed the failure. |
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
The device memory leak validation tests began failing in the Candidate branch after the test-related changes introduced in PR #35487, which exposed actual memory leaks that were previously undetected. The regressions affected the Cells Do Not Leak and Handler Does Not Leak tests on Windows, the Cells Do Not Leak test on iOS, the Handler Does Not Leak test on iOS 26.0, and the Handler Does Not Leak test on macOS 26.0, indicating platform-specific memory leak issues that require fixes.
Root cause
The issue occurs because of iOS 26's new "Liquid Glass" rendering, under which the native stepper control holds an internal, private native retain that isn't reachable or releasable through any public API — confirmed by diagnostics showing the retain count unchanged even after removing the view from its hierarchy and clearing animations.
This was specific to iOS/MacCatalyst 26 only and did not occur on earlier OS versions. A contributing factor was that subview realization and animation were being forced unconditionally, even for the common case where no directional mirroring was needed, adding an extra, avoidable native retain.
Solution description
The fix involves explicitly disposing the native stepper on disconnect, but only for iOS/MacCatalyst 26+ (guarded against being run twice on an already-disposed instance), forcing the interop layer to release its native handle immediately instead of waiting on the garbage collector.
It also involves skipping the subview realization and transform/animation work entirely unless directional mirroring is actually required, while still correctly restoring subviews that were previously mirrored if the layout reverts. The leak test was also updated to explicitly disconnect each control's handler before checking for collection, mirroring how a real app tears down views.
Validated the behavior in the following platforms
Regression PR
PR #35487
Issues Fixed
Fixes #35985
Output ScreenShot
BeforeFix-Stepper-MemoryLeak.Mac.1.1.mov
AfterFix-Handler.Does.not.leak.-.Stepper.Mac.26.1.mov
BeforeFix-Stepper-MemoryLeak.iOS.1.mov
AfterFix-Handler.Does.not.leak.-.Stepper.iOS.26.1.mov