Description
On a VPS running under legacy OpenVZ virtualization, a self-contained .NET application fails to start after updating to .NET Runtime 10.0.12, with:
GC: This process is affinitize to 0 CPUs, check your GC heap affinity related configs
GC heap initialization failed with error 0x8013200A
Failed to create CoreCLR, HRESULT: 0x8013200A
The app previously started fine on an earlier 10.0.11 runtime.
Root cause appears to be that the CPU-count/affinity detection logic combines two data sources that are inconsistent under this legacy OpenVZ configuration, and the Server GC init path ends up computing an effective CPU count of 0, while the Workstation GC path does not hit this and starts normally.
Environment
- .NET Runtime: 10.0.12 (self-contained deployment)
- Target Framework: net10.0
- OS: Linux (guest), virtualization detected as
openvz via systemd-detect-virt
- Deployment type: self-contained executable (no
dotnet CLI/SDK installed on the host)
Diagnostic data collected
== nproc --all ==
4
== /proc/self/status | grep Cpus_allowed ==
Cpus_allowed: ffff,ffffffff,ffffffff,ffffffff
Cpus_allowed_list: 0-111
== systemd-detect-virt ==
openvz
== stat -fc %T /sys/fs/cgroup/ ==
tmpfs
== cgroup v2 cpuset.cpus.effective ==
(empty)
== cgroup v1 cpuset/cpuset.cpus ==
0-111
== GC-related environment variables at time of failure ==
(none set)
Key observation: nproc correctly reports the VPS's actual CPU quota (4), but the process's reported affinity mask (/proc/self/status) and the cgroup cpuset both report the full host range (0-111). /sys/fs/cgroup is mounted as a plain tmpfs, not a real cgroupfs, which is typical of legacy (non-namespace-aware) OpenVZ containers — the cgroup files exist for compatibility but don't reflect real per-container limits.
Repro steps
- Deploy a self-contained net10.0 app on a legacy OpenVZ VPS where:
nproc reports the real CPU quota (e.g. 4), but
/proc/self/status Cpus_allowed reports the full host CPU range, and
/sys/fs/cgroup is a tmpfs stub (not real cgroupfs), with cpuset.cpus/cpuset.cpus.effective also reporting the host range or being empty.
- Run the app with default settings (Server GC enabled, no affinity overrides).
- Observe immediate crash on startup with HRESULT
0x8013200A.
What we tried
| Setting |
Result |
| Default (Server GC, no overrides) |
Fails — 0x8013200A |
DOTNET_PROCESSOR_COUNT=4 + DOTNET_GCHeapCount=4 + DOTNET_GCNoAffinitize=1 (Server GC still on) |
Fails — same error |
DOTNET_gcServer=0 + DOTNET_GCNoAffinitize=1 (Workstation GC) |
Works |
This suggests the issue is specific to the Server GC initialization/affinitization path, not general CPU-count detection — Workstation GC starts fine on the same host with the same conflicting /proc/cgroup data.
Expected behavior
The runtime should either:
- correctly reconcile the CPU count from the available signals (e.g. prefer whatever mechanism
nproc/the actual quota uses) instead of computing an intersection that yields 0, or
- fail gracefully / fall back to a sane non-zero heap/thread count instead of a hard abort, particularly since Workstation GC succeeds on the identical host.
Additional notes
- This is very likely a niche environment (legacy, non-namespace-aware OpenVZ hosting is uncommon today), so reproducing it outside such a host may be difficult. Happy to gather more diagnostics on request.
Description
On a VPS running under legacy OpenVZ virtualization, a self-contained .NET application fails to start after updating to .NET Runtime 10.0.12, with:
The app previously started fine on an earlier 10.0.11 runtime.
Root cause appears to be that the CPU-count/affinity detection logic combines two data sources that are inconsistent under this legacy OpenVZ configuration, and the Server GC init path ends up computing an effective CPU count of 0, while the Workstation GC path does not hit this and starts normally.
Environment
openvzviasystemd-detect-virtdotnetCLI/SDK installed on the host)Diagnostic data collected
Key observation:
nproccorrectly reports the VPS's actual CPU quota (4), but the process's reported affinity mask (/proc/self/status) and the cgroup cpuset both report the full host range (0-111)./sys/fs/cgroupis mounted as a plaintmpfs, not a realcgroupfs, which is typical of legacy (non-namespace-aware) OpenVZ containers — the cgroup files exist for compatibility but don't reflect real per-container limits.Repro steps
nprocreports the real CPU quota (e.g. 4), but/proc/self/statusCpus_allowedreports the full host CPU range, and/sys/fs/cgroupis atmpfsstub (not real cgroupfs), withcpuset.cpus/cpuset.cpus.effectivealso reporting the host range or being empty.0x8013200A.What we tried
0x8013200ADOTNET_PROCESSOR_COUNT=4+DOTNET_GCHeapCount=4+DOTNET_GCNoAffinitize=1(Server GC still on)DOTNET_gcServer=0+DOTNET_GCNoAffinitize=1(Workstation GC)This suggests the issue is specific to the Server GC initialization/affinitization path, not general CPU-count detection — Workstation GC starts fine on the same host with the same conflicting
/proc/cgroup data.Expected behavior
The runtime should either:
nproc/the actual quota uses) instead of computing an intersection that yields 0, orAdditional notes