Skip to content

[iOS] MauiCALayer and StaticCAShapeLayer crash on finalizer thread in Release/AOT builds #33800

@pshoey

Description

@pshoey

Description

When running a .NET MAUI iOS app in Release configuration with AOT compilation, the app crashes with EXC_BAD_ACCESS (SIGSEGV) when MauiCALayer or StaticCAShapeLayer is disposed on the finalizer thread. The crash occurs because the finalizer attempts to call Objective-C methods on native CALayer objects that may have already been deallocated by UIKit.

This does not occur in Debug builds (interpreter mode), only in Release builds with AOT enabled.

Affected Controls

This is a general issue affecting any MAUI control that uses MauiCALayer internally, including but not limited to:

  • Border (with or without CornerRadius)
  • BoxView
  • Frame
  • All Shape controls (Rectangle, Ellipse, Line, Path, Polygon, Polyline, RoundRectangle)
  • Any control with Shadow applied
  • Any control with Clip applied
  • Custom handlers that use CALayer

This is not limited to specific controls - any view that creates a MauiCALayer or StaticCAShapeLayer can trigger this crash when garbage collected.

Steps to Reproduce

  1. Create a MAUI app with any controls that use MauiCALayer internally (e.g., Border, BoxView, shapes, shadows)
  2. Build for iOS in Release configuration
  3. Deploy via TestFlight or Ad Hoc
  4. Use the app normally - navigate between pages, scroll CollectionView/ListView, etc.
  5. App crashes within seconds to minutes as GC collects views

Expected Behavior

The app should not crash. The finalizer should not attempt to call into native objects it does not own the lifecycle of.

Actual Behavior

The app crashes with EXC_BAD_ACCESS on the finalizer thread when garbage collection runs.

Crash Log 1 - StaticCAShapeLayer

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000a720c58f0
Termination Reason: SIGNAL 11 Segmentation fault: 11
Triggered by Thread:  3

Thread 3 Crashed:
0   libobjc.A.dylib               objc_msgSend
1   PodcastApp                    @objc MauiCALayerAutosizeToSuperLayerBehavior.detach()
2   PodcastApp                    xamarin_dyn_objc_msgSend
3   PodcastApp                    Microsoft_Maui_ApiDefinition_Messaging__void_objc_msgSend
4   PodcastApp                    Microsoft_Maui_Microsoft_Maui_Platform_StaticCAShapeLayer__Dispose (StaticCAShapeLayer.cs:14)
5   PodcastApp                    Microsoft_iOS_Foundation_NSObject__Finalize (NSObject2.cs:306)
6   PodcastApp                    S_P_CoreLib_System_Runtime___Finalizer__DrainQueue (__Finalizer.cs:57)
7   PodcastApp                    FinalizerStart(void*) (FinalizerHelpers.cpp:83)

Crash Log 2 - MauiCALayer

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x003ee37bca571d00
Termination Reason: SIGNAL 11 Segmentation fault: 11
Triggered by Thread:  2

Thread 2 Crashed:
0   libobjc.A.dylib               objc_msgSend
1   PodcastApp                    xamarin_dyn_objc_msgSend
2   PodcastApp                    Microsoft_Maui_ApiDefinition_Messaging__void_objc_msgSend
3   PodcastApp                    Microsoft_Maui_Microsoft_Maui_Platform_MauiCALayer__Dispose (MauiCALayer.cs:46)
4   PodcastApp                    Microsoft_iOS_Foundation_NSObject__Finalize (NSObject2.cs:306)
5   PodcastApp                    S_P_CoreLib_System_Runtime___Finalizer__DrainQueue (__Finalizer.cs:57)
6   PodcastApp                    FinalizerStart(void*) (FinalizerHelpers.cpp:83)

Root Cause Analysis

The fundamental issue is that MauiCALayer.Dispose() and StaticCAShapeLayer.Dispose() call into Objective-C (objc_msgSend) during finalization, but the managed wrapper does not own the lifecycle of the native CALayer.

UIKit manages the lifecycle of CALayer objects - they are deallocated on the main thread when their owning view is removed from the view hierarchy. The managed MauiCALayer wrapper is collected separately by the .NET GC, potentially after UIKit has already deallocated the native object.

When the finalizer runs:

  1. The GC determines the managed MauiCALayer wrapper is unreachable
  2. The finalizer thread calls Dispose(false)
  3. Dispose() attempts to call ObjC methods (e.g., MauiCALayerAutosizeToSuperLayerBehavior.detach())
  4. The native CALayer has already been deallocated by UIKit
  5. objc_msgSend accesses freed memory → EXC_BAD_ACCESS

This is not a threading issue that can be solved by marshalling to the main thread. The native object is already deallocated - calling into it from any thread will crash.

The issue manifests more aggressively in AOT/Release builds because:

  • AOT compilation results in more aggressive GC timing
  • Objects become eligible for collection sooner than in interpreter mode
  • The finalizer thread runs more frequently

Proposed Fix

The Dispose() methods in MauiCALayer, StaticCAShapeLayer, and related classes should not call into native objects during finalization:

protected override void Dispose(bool disposing)
{
    if (disposing)
    {
        // Explicit Dispose() call - safe to clean up native resources
        // because the caller is responsible for ensuring the object is still valid
        PerformNativeCleanup();
    }
    // When finalizing (!disposing), do NOT touch native objects.
    // UIKit owns the CALayer lifecycle and may have already deallocated it.
    // The OS will reclaim the native memory regardless.
    
    base.Dispose(disposing);
}

This follows the standard .NET disposable pattern where the finalizer should only release unmanaged resources that the object owns, not call into external frameworks that manage their own object lifecycles.

Environment

  • .NET SDK: 10.0.102
  • .NET Runtime: Microsoft.NETCore.App 10.0.2
  • .NET MAUI: 10.0.30 (also reproduced on 10.0.20)
  • Xcode: 26.2
  • Build Configuration: Release with AOT
  • Distribution: TestFlight

Tested on:

Device iOS Version Result
iPhone 16 Pro (iPhone17,1) 26.2.1 Crash confirmed
iPhone 14 Plus (iPhone14,8) 26.2 Crash confirmed
iPhone 11 Pro (iPhone12,3) 18.7.1 Crash confirmed

Tested across multiple app builds (73, 81, 83, 84) with consistent crashes.

Related Issues

Workaround

Currently no reliable workaround. The issue affects core MAUI layer infrastructure used by many controls.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions