Skip to content

[iOS 26] NavigationRenderer.RemoveViewControllers continuation throws ObjectDisposedException after the renderer is disposed #39391

Closed

Description

@tomcurran

Description

On iOS 26, NavigationRenderer.RemoveViewControllers can throw ObjectDisposedException from a continuation that runs after the renderer has been disposed. Nothing observes that continuation's task, so the exception surfaces later through TaskScheduler.UnobservedTaskException, which apps that report unobserved exceptions record as an error.

RemoveViewControllers runs from the PopViewController / PopToRootViewController overrides, i.e. a native back tap or swipe-back. It attaches a continuation to the popped page's appeared/disappeared task, queued on the synchronization context. When the task completes with false, the iOS 26 branch reads ViewControllers on the renderer to decide whether to dispose the popped controller:

if (OperatingSystem.IsIOSVersionAtLeast(26) || OperatingSystem.IsMacCatalystVersionAtLeast(26))
{
    if (controller is not null && !ViewControllers.Contains(controller))   // renderer may be disposed by now
        controller.Dispose();
}

That read doesn't check _disposed. The task completes with false on the popped page's Disappearing (the normal case for a pop), and ViewDidDisappear also completes it with false (CompletePendingNavigation(false)) when the navigation controller itself goes away, for example when a modal NavigationPage is dismissed. So the continuation runs about two main-loop turns later. If the renderer's native UINavigationController has been released by then, ViewControllers throws.

Before iOS 26 the continuation only disposes the captured controller and never touches the renderer, so this is specific to the iOS 26 branch. It appears to have come in with #34595 (merged 23 Mar 2026), which limited that disposal to controllers no longer in ViewControllers. That's inferred from the PR description.

Seen once in production (MAUI 10.0.100, iPhone 12, iOS 26.6.2), in about 1,500 iOS 26 sessions. The app uses a NavigationRenderer subclass that only overrides ViewWillAppear, which is why its name appears in the exception. Users see nothing: the pop and the dismissal have already finished, and the skipped work is disposing a controller that the renderer's own Dispose handles anyway. The cost is a spurious error in crash reporting.

Steps to Reproduce

I haven't been able to reproduce it on demand; it looks like a narrow timing window.

The intended scenario:

  1. Present a NavigationPage modally and push a second page.
  2. Pop the top page natively (back tap, or UINavigationController.PopViewController(true)).
  3. Dismiss the modal straight away.
  4. If the navigation controller's native object is released before the continuation runs, ViewControllers throws ObjectDisposedException in the continuation.

What I tried, all on an iOS 26.5 simulator with MAUI 10.0.110 (about 1,900 iterations in total, no hits):

  • A plain MAUI app running that loop: animated and non-animated pops and dismissals; closing the modal from the popped page's OnDisappearing; disposing the renderer in the same turn; close delays from 0 to 350 ms.
  • The production app driving the same scenario from code, through its navigation library (Prism), with closes 0–2 main-loop turns or 16–400 ms after the pop, and an immediate DisconnectHandler() after a non-animated close.

Instrumenting those runs showed the navigation controller's native handle being released only 0.6–4 s after the pop. The modal wrapper seems to keep it alive until a garbage collection, so in every flow I could drive, the continuation read a controller that was still alive. My best guess is that the production event needed a GC to free it in exactly that window.

Link to public reproduction project repository

None. I couldn't build one that reproduces it. The report rests on the code path above and the production stack below.

Version with bug

10.0.100

Is this a regression from previous behavior?

Yes, this used to work in .NET MAUI

Last version that worked well

Unknown. The iOS 26 branch isn't in 10.0.20, and it is in 10.0.100, 10.0.110 and main.

Affected platforms

iOS (Mac Catalyst 26 takes the same branch, but I haven't checked it)

Affected platform versions

iOS 26

Did you find any workaround?

The app filters this specific exception (an ObjectDisposedException whose object name ends in NavigationRenderer) out of its TaskScheduler.UnobservedTaskException reporting.

A possible fix: return early in the continuation when the renderer is disposed:

task.ContinueWith(t =>
{
    if (t.Result || _disposed)
        return;
    ...

This should cover every case. The continuation holds the renderer, so its native object can only be released after the renderer's own Dispose runs, and that sets _disposed first. The fix keeps #34595's behaviour.

Relevant log output

Stack as recorded in production (exception resource strings are trimmed in that build):

System.AggregateException: TaskExceptionHolder_UnhandledException
(Inner Exception #0) System.ObjectDisposedException: ObjectDisposed_Generic
ObjectDisposed_ObjectName_Name, <App>.Platforms.iOS.Handlers.SafeNavigationRenderer
  at ObjCRuntime.ThrowHelper.ThrowObjectDisposedException(Object)
  at Foundation.NSObject.get_SuperHandle()
  at UIKit.UINavigationController.get_ViewControllers()
  at Microsoft.Maui.Controls.Handlers.Compatibility.NavigationRenderer.<>c__DisplayClass87_0.<RemoveViewControllers>b__0(Task`1 t)
  at System.Threading.Tasks.ContinuationTaskFromResultTask`1[[System.Boolean]].InnerInvoke()
  at System.Threading.Tasks.Task.<>c.<.cctor>b__288_0(Object obj)
  at System.Threading.ExecutionContext.RunInternal(ExecutionContext, ContextCallback, Object)
  at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task&, Thread)

Activity

  1. sheiksyedm commented on Oct 15, 2026

    @sheiksyedm
  2. added
    s/triagedIssue has been reviewed
    and removed
    s/needs-verificationIndicates that this issue needs initial verification before further triage will happen
    potential-regressionThis issue described a possible regression on a currently supported version., verification pending
    on Oct 19, 2026
  3. added a commit that references this issue on Oct 21, 2026
    47dc897
  4. kubaflo commented on Oct 25, 2026

    @kubaflo
    Contributor

    Fixed here #39458

  5. added 2 commits that reference this issue on Oct 25, 2026
    f71badc
    d1eb1af
  6. added a commit that references this issue on Oct 28, 2026
    18c5cc4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions