Skip to content

[macOS] .NET 11 shutdown crash when AvaloniaNative releases managed dispatcher after CoreCLR thread teardown #22062

Description

@aelij

Describe the bug

After upgrading an Avalonia application from .NET 10 to .NET 11, the process crashes during normal application shutdown on macOS.

The application window closes normally, but process teardown subsequently ends with SIGABRT. The failure occurs when the native static ComPtr<IAvnDispatcher> is destroyed and releases its managed implementation after CoreCLR has already detached the current thread from its new Epoch-Based Reclamation infrastructure.

This appears to be a shutdown-ordering issue in Avalonia Native. Its native platform cleanup is registered through AppDomain.ProcessExit, but the dispatcher callback remains reachable from native static storage late enough to be released after managed execution is no longer safe.

Crash stack

The relevant part of the macOS crash report is:

Exception Type:    EXC_CRASH (SIGABRT)
Termination Reason: Namespace SIGNAL, Code 6, Abort trap: 6

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libsystem_kernel.dylib       __pthread_kill
1   libsystem_pthread.dylib      pthread_kill
2   libsystem_c.dylib            abort
3   libcoreclr.dylib             PROCAbort
4   libcoreclr.dylib             RaiseFailFastException
5   libcoreclr.dylib             FailFastOnAssert()
6   libcoreclr.dylib             __FreeBuildAssertFail
7   libcoreclr.dylib             EbrCollector::EnterCriticalRegion()
8   libcoreclr.dylib             HashMap::LookupValueByUniqueKey(...)
9   libcoreclr.dylib             TypeIDMap::LookupType(...)
10  libcoreclr.dylib             VSD_ResolveWorker
11  libcoreclr.dylib             ResolveWorkerAsmStub
...
14  libAvaloniaNative.dylib      ComPtr<IAvnDispatcher>::~ComPtr()
15  libsystem_c.dylib            __cxa_finalize_ranges
16  libsystem_c.dylib            exit

The crash occurs after exit() has begun, while running native static destructors.

Relevant .NET 11 change

.NET 11 changed CoreCLR's internal asynchronous HashMap protection from cooperative GC transitions to Epoch-Based Reclamation:

Virtual stub dispatch uses this hash map through the following path:

VSD_ResolveWorker
TypeIDMap::LookupType
HashMap::LookupValueByUniqueKey
EbrCollector::EnterCriticalRegion

When CoreCLR tears down a thread, the EBR TLS destructor detaches it and poisons its collector state with DetachedCollector. A subsequent managed virtual call on that thread reaches this all-build assertion:

_ASSERTE_ALL_BUILDS(
    pData->m_pCollector != DetachedCollector &&
    "Attempt to reattach detached thread.");

Avalonia Native's late release of IAvnDispatcher causes exactly that sequence.

Under .NET 10, the same late managed callback did not use EBR, so the unsafe shutdown ordering did not fail deterministically.

Avalonia cleanup path

AvaloniaNativePlatform currently registers cleanup through AppDomain.ProcessExit:

AppDomain.CurrentDomain.ProcessExit += OnProcessExit;

The handler disposes the native factory:

private void OnProcessExit(object? sender, EventArgs e)
{
    AppDomain.CurrentDomain.ProcessExit -= OnProcessExit;
    _factory.Dispose();
}

The native factory destructor clears the static dispatcher:

virtual ~AvaloniaNative() override
{
    ReleaseAvnAppEvents();
    _deallocator = nullptr;
    _dispatcher = nullptr;
}

However, the crash demonstrates that the static _dispatcher can still be released from native finalization after CoreCLR has detached the thread.

To Reproduce

Using a standard classic desktop lifetime (file-based app):

#:property TargetFramework=net11.0-macos
#:property SupportedOSPlatformVersion=15.0
#:property ApplicationId=com.example.avalonia-crash-repro
#:property ApplicationTitle=Avalonia Crash Repro
#:package Avalonia.Desktop@12.1.1

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;

namespace AvaloniaCrashRepro;

internal static class Program
{
    [STAThread]
    public static int Main(string[] args) =>
        AppBuilder.Configure<App>()
            .UsePlatformDetect()
            .StartWithClassicDesktopLifetime(args);
}

internal sealed class App : Application
{
    public override void OnFrameworkInitializationCompleted()
    {
        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            desktop.MainWindow = new Window
            {
                Title = "Avalonia Crash Repro",
                Width = 480,
                Height = 240,
                Content = new TextBlock
                {
                    Text = "Close this window to reproduce the shutdown crash.",
                    Margin = new Thickness(24)
                }
            };
        }

        base.OnFrameworkInitializationCompleted();
    }
}
  1. Build the application for net11.0-macos.
  2. Start it normally.
  3. Close the main window or quit the application.
  4. The window closes.
  5. macOS reports that the application terminated unexpectedly.
  6. A crash report is generated with the stack above.

In my application, this reproduces on every normal shutdown.

Workaround and confirmation

As a diagnostic workaround, I invoked Avalonia Native's existing OnProcessExit cleanup immediately after StartWithClassicDesktopLifetime returned, while managed execution was still valid:

var exitCode = BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);

// Invoke AvaloniaNativePlatform.OnProcessExit here.

return exitCode;

Before applying that workaround, every shutdown generated a new macOS .ips crash report.

After moving the same native cleanup before Main returns:

  • The application exited normally.
  • No new crash report was generated.
  • The CoreCLR EBR assertion no longer occurred.

This suggests that the native resources need to be released during normal Avalonia application or dispatcher shutdown, rather than relying exclusively on AppDomain.ProcessExit or native static destruction.

Expected behavior

An Avalonia desktop application should exit cleanly under .NET 11 without crashing.

Avalonia version

12.1.1

OS

macOS

Additional context

Environment

Avalonia:          12.1.1
Target framework:  net11.0-macos
.NET SDK:          11.0.100-preview.7.26381.103
.NET runtime:      11.0.0-preview.7.26381.103
OS:                macOS 26.5.2
Architecture:      Apple Silicon / arm64

Related issues

Activity

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

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions