Repository navigation
GCHeapAffinitizeRanges is completely ignored under NativeAOT #128396
Description
Description
When publishing with NativeAOT (PublishAot=true), the GCHeapAffinitizeRanges configuration is ignored regardless of how it is supplied — RuntimeHostConfigurationOption in the .csproj, IlcArg --runtimeopt:, or the DOTNET_GCHeapAffinitizeRanges environment variable. The Server GC heap threads fall back to the default per-heap affinity (heap 0 → CPU 0, heap 1 → CPU 1, etc.) instead of the user-specified ranges. The same configuration works correctly in non-AOT (JIT) builds. Other GC configs like GCHeapCount and gcServer are honored in NativeAOT, singling this out as specific to HeapAffinitizeRanges.
Reproduction Steps
- Create a minimal console app targeting net10.0 with PublishAot=true:
<!-- GCHeapAotBug.csproj -->
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<PublishAot>true</PublishAot>
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<ItemGroup>
<RuntimeHostConfigurationOption Include="System.GC.HeapCount" Value="1" />
<RuntimeHostConfigurationOption Include="System.GC.HeapAffinitizeRanges" Value="7" />
</ItemGroup>
</Project>
// Program.cs
using System.Runtime;
using System.Runtime.InteropServices;
Console.WriteLine($"PID: {Environment.ProcessId}");
Console.WriteLine($"Server GC: {GCSettings.IsServerGC}");
// Keep GC active
var list = new List<byte[]>();
for (int i = 0; i < 60; i++)
{
list.Add(new byte[10 * 1024 * 1024]);
if (list.Count > 20) list.RemoveRange(0, 5);
if (i % 5 == 0) { GC.Collect(); GC.WaitForPendingFinalizers(); }
Thread.Sleep(1000);
}- Publish and run:
dotnet publish -c Release -o ./publish
./publish/GCHeapAotBug &
PID=$!
cat /proc/$PID/task/*/status | grep -B1 Cpus_allowed_list
- Also confirmed that IlcArg --runtimeopt: and the DOTNET_GCHeapAffinitizeRanges env var both fail identically on the AOT binary.
Expected behavior
The .NET Server GC thread should have affinity restricted to CPU 7 (i.e. Cpus_allowed_list: 7), matching the configured GCHeapAffinitizeRanges=7 with GCHeapCount=1.
Actual behavior
The .NET Server GC thread ends up with Cpus_allowed_list: 0 — the default first-CPU binding. pidstat confirms the thread only ever runs on CPU 0, never CPU 7:
| TID | NAME | AFFINITY | CPU |
|---|---|---|---|
| 2561246 | .NET Server GC | 0 | 0 |
GCHeapCount=1 is honored (only 1 heap thread), proving configuration plumbing works. Only the affinity range is dropped.
Regression?
Unknown.
This is the first time testing this specific combination. It works correctly in non-AOT builds.
Known Workarounds
None.
Neither RuntimeHostConfigurationOption, IlcArg --runtimeopt:, nor DOTNET_GCHeapAffinitizeRanges nvironment variable overcome the issue on NativeAOT binaries. The only path is to not use NativeAOT.
Configuration
- .NET 10.0.102
- Ubuntu 22.04.5 LTS (WSL2, kernel 6.6.87.2)
- x64, 24 CPUs
- ServerGarbageCollection=true, PublishAot=true, RuntimeIdentifier=linux-x64
- The issue is specific to NativeAOT. Non-AOT (JIT) builds of the same code respect GCHeapAffinitizeRanges.
Other information
Related but not identical issues: #85961 (GC configs ignored in NativeAOT, fixed for HeapHardLimit) and #92458 some GC settings not honored, deferred to "Future" milestone). This appears to be an un-reported gap specific to the HeapAffinitizeRanges knob in the NativeAOT GC implementation.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
dotnet-policy-service commentedon May 20, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @dotnet/gc
See info in area-owners.md if you want to be subscribed.
janvorli commentedon May 20, 2026
@yang-xiaodong thank you for reporting this. I've looked into it and found that the issue is caused by this:
runtime/src/coreclr/nativeaot/Runtime/gcenv.ee.cpp
Lines 798 to 805 in 3366f6a
As a workaround until it is fixed, you can use other form to specify the affinitization. There is GCHeapAffinitizeMask knob that allows you to specify the affinitization as a bitmask (in a form of number), see https://learn.microsoft.com/en-us/Dotnet/framework/configure-apps/file-schema/runtime/gcheapaffinitizemask-element
The only disadvantage is that it allows you to specify bitmask for first 64 CPUs only.
Fix NativeAOT string GC config handling (#128455)
[release/10.0] Fix NativeAOT string GC config handling (#128894)
Fix NativeAOT string GC config handling (#128455)