From 4b0ced1bb37997ffd7376fa9417b1ab41bd509ec Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Tue, 12 May 2026 12:39:28 +0300 Subject: [PATCH 1/2] Fix runtime initialisation on linux with cpu hotplug enabled 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. --- src/coreclr/gc/unix/gcenv.unix.cpp | 3 ++- .../nativeaot/Runtime/unix/PalUnix.cpp | 5 ++-- src/coreclr/pal/src/misc/sysinfo.cpp | 5 ++-- src/coreclr/pal/src/thread/thread.cpp | 5 ++-- src/native/minipal/CMakeLists.txt | 4 +++ src/native/minipal/cpucount.c | 26 +++++++++++++++++++ src/native/minipal/cpucount.h | 24 +++++++++++++++++ 7 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 src/native/minipal/cpucount.c create mode 100644 src/native/minipal/cpucount.h diff --git a/src/coreclr/gc/unix/gcenv.unix.cpp b/src/coreclr/gc/unix/gcenv.unix.cpp index 98b0588e2083ae..d087ee6c5074b2 100644 --- a/src/coreclr/gc/unix/gcenv.unix.cpp +++ b/src/coreclr/gc/unix/gcenv.unix.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #if HAVE_SWAPCTL #include @@ -146,7 +147,7 @@ bool GCToOSInterface::Initialize() return false; } - int configuredCpuCount = sysconf(_SC_NPROCESSORS_CONF); + int configuredCpuCount = minipal_get_cpu_max_possible_count(); if (configuredCpuCount == -1) { return false; diff --git a/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp b/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp index dec55db82905fb..6d7ae3bc96f7da 100644 --- a/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp +++ b/src/coreclr/nativeaot/Runtime/unix/PalUnix.cpp @@ -28,6 +28,7 @@ #include "RhConfig.h" #include +#include #include #include #include @@ -456,10 +457,10 @@ void InitializeCurrentProcessCpuCount() { #if HAVE_SCHED_GETAFFINITY - int configuredCpuCount = sysconf(_SC_NPROCESSORS_CONF); + int configuredCpuCount = minipal_get_cpu_max_possible_count(); if (configuredCpuCount == -1) { - // In the unlikely event that sysconf(_SC_NPROCESSORS_CONF) fails, just assume a reasonable default maximum number of CPUs to avoid failing. + // In the unlikely event that minipal_get_cpu_max_possible_count() fails, just assume a reasonable default maximum number of CPUs to avoid failing. configuredCpuCount = CPU_SETSIZE; } diff --git a/src/coreclr/pal/src/misc/sysinfo.cpp b/src/coreclr/pal/src/misc/sysinfo.cpp index 63b02d00be88d1..f928f839f85c2f 100644 --- a/src/coreclr/pal/src/misc/sysinfo.cpp +++ b/src/coreclr/pal/src/misc/sysinfo.cpp @@ -26,6 +26,7 @@ Revision History: #include #include #include +#include #define __STDC_FORMAT_MACROS #include #include @@ -157,10 +158,10 @@ PAL_GetLogicalCpuCountFromOS() { #if HAVE_SCHED_GETAFFINITY - int configuredCpuCount = sysconf(_SC_NPROCESSORS_CONF); + int configuredCpuCount = minipal_get_cpu_max_possible_count(); if (configuredCpuCount == -1) { - // In the unlikely event that sysconf(_SC_NPROCESSORS_CONF) fails, just assume a reasonable default maximum number of CPUs to avoid failing. + // In the unlikely event that minipal_get_cpu_max_possible_count() fails, just assume a reasonable default maximum number of CPUs to avoid failing. configuredCpuCount = CPU_SETSIZE; } diff --git a/src/coreclr/pal/src/thread/thread.cpp b/src/coreclr/pal/src/thread/thread.cpp index 611758bd356ed5..7e9c3baa6a0cd1 100644 --- a/src/coreclr/pal/src/thread/thread.cpp +++ b/src/coreclr/pal/src/thread/thread.cpp @@ -28,6 +28,7 @@ SET_DEFAULT_DEBUG_CHANNEL(THREAD); // some headers have code with asserts, so do #include "pal/virtual.h" #include +#include #if defined(__NetBSD__) && !HAVE_PTHREAD_GETCPUCLOCKID #include @@ -1392,10 +1393,10 @@ CPalThread::ThreadEntry( // - https://forum.snapcraft.io/t/requesting-autoconnect-for-interfaces-in-pigmeat-process-control-home/17987/13 { - int configuredCpuCount = sysconf(_SC_NPROCESSORS_CONF); + int configuredCpuCount = minipal_get_cpu_max_possible_count(); if (configuredCpuCount == -1) { - // In the unlikely event that sysconf(_SC_NPROCESSORS_CONF) fails, just assume a reasonable default maximum number of CPUs to avoid failing thread creation. + // 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; } diff --git a/src/native/minipal/CMakeLists.txt b/src/native/minipal/CMakeLists.txt index af1990a9909fc4..25bdd2c0451538 100644 --- a/src/native/minipal/CMakeLists.txt +++ b/src/native/minipal/CMakeLists.txt @@ -24,6 +24,10 @@ if(NOT WIN32 AND NOT CLR_CMAKE_TARGET_ARCH_WASM AND NOT HOST_WASM) list(APPEND SOURCES ospagesize.c) endif() +if(CLR_CMAKE_HOST_UNIX) + list(APPEND SOURCES cpucount.c) +endif() + # Provide an object library for scenarios where we ship static libraries include_directories(${CLR_SRC_NATIVE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/native/minipal/cpucount.c b/src/native/minipal/cpucount.c new file mode 100644 index 00000000000000..a53c352e438e62 --- /dev/null +++ b/src/native/minipal/cpucount.c @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#include "cpucount.h" + +#include +#include + +int minipal_get_cpu_max_possible_count(void) +{ +#if defined(__linux__) + int hi; + FILE* f = fopen("/sys/devices/system/cpu/possible", "r"); + if (f != NULL) + { + if (fscanf(f, "%*d-%d", &hi) == 1) + { + fclose(f); + return hi + 1; + } + fclose(f); + } +#endif + + return (int)sysconf(_SC_NPROCESSORS_CONF); +} diff --git a/src/native/minipal/cpucount.h b/src/native/minipal/cpucount.h new file mode 100644 index 00000000000000..5b2d6afa59bf33 --- /dev/null +++ b/src/native/minipal/cpucount.h @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef HAVE_MINIPAL_CPUCOUNT_H +#define HAVE_MINIPAL_CPUCOUNT_H + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +// Returns the maximum number of CPUs that could ever be available on this system, +// suitable for sizing cpu_set_t allocations via CPU_ALLOC. +// +// On Linux, this reads /sys/devices/system/cpu/possible to account for CPU hotplug. +// This may be larger than the number of online or present CPUs. +// Falls back to sysconf(_SC_NPROCESSORS_CONF) if the sysfs file is unavailable. +int minipal_get_cpu_max_possible_count(void); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // HAVE_MINIPAL_CPUCOUNT_H From 2d241c7125d53babff420c8600c428b79036aeb7 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Tue, 12 May 2026 17:29:53 +0300 Subject: [PATCH 2/2] Add support for reading a list for cpu ranges. Ex: 0-1,3-4,6,8-10. It is not obvious in which scenario the kernel would report this, better safe than sorry. --- src/native/minipal/cpucount.c | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/native/minipal/cpucount.c b/src/native/minipal/cpucount.c index a53c352e438e62..0298724a8b7d65 100644 --- a/src/native/minipal/cpucount.c +++ b/src/native/minipal/cpucount.c @@ -9,16 +9,39 @@ int minipal_get_cpu_max_possible_count(void) { #if defined(__linux__) - int hi; FILE* f = fopen("/sys/devices/system/cpu/possible", "r"); if (f != NULL) { - if (fscanf(f, "%*d-%d", &hi) == 1) + int maxCpu = -1; + for (;;) { - fclose(f); - return hi + 1; + int lo, hi; + int matched = fscanf(f, "%d-%d", &lo, &hi); + if (matched == 1) + { + hi = lo; + } + else if (matched != 2) + { + break; + } + + if (maxCpu < hi) + { + maxCpu = hi; + } + + int ch = fgetc(f); + if (ch == EOF || ch != ',') + { + break; + } } fclose(f); + if (maxCpu != -1) + { + return maxCpu + 1; + } } #endif