Fix runtime initialization on linux with cpu hotplug enabled - #128069
Conversation
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
Pull request overview
This PR introduces a shared native helper to determine an upper bound for CPU indices on Unix (especially Linux with CPU hotplug) and uses it to size cpu_set_t allocations for sched_getaffinity/sched_setaffinity, avoiding initialization failures due to undersized affinity masks.
Changes:
- Added
minipal_get_cpu_max_possible_count()in minipal to provide a “max possible CPU count” forCPU_ALLOCsizing. - Updated CoreCLR PAL, GC Unix init, and NativeAOT Unix PAL to use the new helper instead of calling
sysconf(_SC_NPROCESSORS_CONF)directly. - Wired the new minipal source into the minipal CMake build on Unix.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/native/minipal/cpucount.h | Declares new minipal CPU max-possible count helper. |
| src/native/minipal/cpucount.c | Implements Linux sysfs-based lookup with sysconf fallback. |
| src/native/minipal/CMakeLists.txt | Adds cpucount.c to minipal sources on Unix builds. |
| src/coreclr/pal/src/thread/thread.cpp | Uses helper to size affinity mask when resetting thread affinity. |
| src/coreclr/pal/src/misc/sysinfo.cpp | Uses helper when sizing affinity mask for logical CPU count detection. |
| src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp | Uses helper when sizing affinity mask for NativeAOT CPU count initialization. |
| src/coreclr/gc/unix/gcenv.unix.cpp | Uses helper for affinity set sizing during GC Unix initialization. |
Comments suppressed due to low confidence (1)
src/coreclr/pal/src/thread/thread.cpp:1405
minipal_get_cpu_max_possible_count()is called on every thread start inThreadEntry, and on Linux it currently opens/parses a sysfs file each time. Thread creation can be frequent (threadpool, timers), so this adds avoidable syscalls/I/O to a hot-ish path. Consider caching the computed max-possible CPU count (e.g., a static cached value in minipal with a benign race, orpthread_once) so subsequent calls are just a load.
// In the unlikely event that minipal_get_cpu_max_possible_count() fails, just assume a reasonable default maximum number of CPUs to avoid failing thread creation.
configuredCpuCount = CPU_SETSIZE;
}
cpu_set_t* pCpuSet = CPU_ALLOC(configuredCpuCount);
if (pCpuSet == nullptr)
{
|
@janvorli @jkotas I have a linux vm that has cpu hotplug enabled by default. On my machine, the contents of There doesn't seem to be a way to obtain this value via the OS api. From my understanding, there is this ordering: possible cpus >= configured cpus >= online cpus. When using the |
|
What about code like ? Do we expect to be able to cope with these values changing? |
|
The doc at https://learn.microsoft.com/en-us/dotnet/api/system.environment.processorcount?view=netframework-4.8.1&viewFallbackFrom=net-10.0 suggests that this API makes no effort to deal with changes in configuration/affinity. |
On unix, during initialisation, the runtime obtains the total number of CPUs via `sysconf(_SC_NPROCESSORS_CONF)`. This should return the current number of cpus that are currently present on the system. It turns out linux has cpu hotplug support, so this number can increase. When hotplug is enabled, the kernel reserves storage for the max possible number of CPUs. This max number is exported in `/sys/devices/system/cpu/possible`. The problem is that, when allocating the `cpu_set_t*` for use with `sched_getaffinity`, this api failed because the OS expected for the cpu set to have reserved space for the maximum amount of cpu's, not just for the ones that are currently present.
|
@BrzVlad that means the doc for sysconf is incorrect. It says that _SC_NPROCESSORS_ONLN would get the number of processors that are online and _SC_NPROCESSORS_CONF the number of processors configured in the system. The doc for
This seems to indicate that the _SC_NPROCESSORS_CONF should include even CPUs that can be hot plugged, but are currently not. |
|
Some more context. Fixed in glibc 2.36 -> bminor/glibc@97a912f |
|
Ok, since we want to support even older glibc versions, we need to make this fix. |
Ex: 0-1,3-4,6,8-10. It is not obvious in which scenario the kernel would report this, better safe than sorry.
…128069) On unix, during initialization, the runtime obtains the total number of CPUs via `sysconf(_SC_NPROCESSORS_CONF)`. This should return the current number of cpus that are currently present on the system. It turns out linux has cpu hotplug support, so this number can increase. When hotplug is enabled, the kernel reserves storage for the max possible number of CPUs. This max number is exported in `/sys/devices/system/cpu/possible`. The problem is that, when allocating the `cpu_set_t*` for use with `sched_getaffinity`, this api failed because the OS expected for the cpu set to have reserved space for the maximum amount of cpu's, not just for the ones that are currently present.
…128069) On unix, during initialization, the runtime obtains the total number of CPUs via `sysconf(_SC_NPROCESSORS_CONF)`. This should return the current number of cpus that are currently present on the system. It turns out linux has cpu hotplug support, so this number can increase. When hotplug is enabled, the kernel reserves storage for the max possible number of CPUs. This max number is exported in `/sys/devices/system/cpu/possible`. The problem is that, when allocating the `cpu_set_t*` for use with `sched_getaffinity`, this api failed because the OS expected for the cpu set to have reserved space for the maximum amount of cpu's, not just for the ones that are currently present.
On unix, during initialization, the runtime obtains the total number of CPUs via
sysconf(_SC_NPROCESSORS_CONF). This should return the current number of cpus that are currently present on the system. It turns out linux has cpu hotplug support, so this number can increase. When hotplug is enabled, the kernel reserves storage for the max possible number of CPUs. This max number is exported in/sys/devices/system/cpu/possible. The problem is that, when allocating thecpu_set_t*for use withsched_getaffinity, this api failed because the OS expected for the cpu set to have reserved space for the maximum amount of cpu's, not just for the ones that are currently present.