From 2583f31499a25263c4de495e15c78cdb66afc8d5 Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Mon, 29 Jun 2026 12:40:48 -0700 Subject: [PATCH 1/3] Add mutex and refcounting for init and deinit --- src/init.c | 104 ++++++++++++++++++++++++--------------------------- test/init.cc | 57 ++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 56 deletions(-) diff --git a/src/init.c b/src/init.c index e71b1867..e3cd9217 100644 --- a/src/init.c +++ b/src/init.c @@ -13,106 +13,98 @@ #endif #if defined(_WIN32) || defined(__CYGWIN__) -static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT; -static INIT_ONCE deinit_guard = INIT_ONCE_STATIC_INIT; +static SRWLOCK cpuinfo_lifecycle_lock = SRWLOCK_INIT; +#define CPUINFO_LIFECYCLE_LOCK() AcquireSRWLockExclusive(&cpuinfo_lifecycle_lock) +#define CPUINFO_LIFECYCLE_UNLOCK() ReleaseSRWLockExclusive(&cpuinfo_lifecycle_lock) #elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__) -static pthread_once_t init_guard = PTHREAD_ONCE_INIT; -static pthread_once_t deinit_guard = PTHREAD_ONCE_INIT; +static pthread_mutex_t cpuinfo_lifecycle_lock = PTHREAD_MUTEX_INITIALIZER; +#define CPUINFO_LIFECYCLE_LOCK() pthread_mutex_lock(&cpuinfo_lifecycle_lock) +#define CPUINFO_LIFECYCLE_UNLOCK() pthread_mutex_unlock(&cpuinfo_lifecycle_lock) #else -static bool init_guard = false; -static bool deinit_guard = false; +/* Single-threaded build (e.g. Emscripten without pthreads): no lock needed. */ +#define CPUINFO_LIFECYCLE_LOCK() \ + do { \ + } while (0) +#define CPUINFO_LIFECYCLE_UNLOCK() \ + do { \ + } while (0) #endif +static unsigned int cpuinfo_init_refcount = 0; bool CPUINFO_ABI cpuinfo_initialize(void) { + CPUINFO_LIFECYCLE_LOCK(); + if (cpuinfo_init_refcount == 0) { #if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64 #if defined(__MACH__) && defined(__APPLE__) - pthread_once(&init_guard, &cpuinfo_x86_mach_init); + cpuinfo_x86_mach_init(); #elif defined(__FreeBSD__) - pthread_once(&init_guard, &cpuinfo_x86_freebsd_init); + cpuinfo_x86_freebsd_init(); #elif defined(__linux__) - pthread_once(&init_guard, &cpuinfo_x86_linux_init); + cpuinfo_x86_linux_init(); #elif defined(_WIN32) || defined(__CYGWIN__) - InitOnceExecuteOnce(&init_guard, &cpuinfo_x86_windows_init, NULL, NULL); + cpuinfo_x86_windows_init(NULL, NULL, NULL); #else - cpuinfo_log_error("operating system is not supported in cpuinfo"); + cpuinfo_log_error("operating system is not supported in cpuinfo"); #endif #elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 #if defined(__linux__) - pthread_once(&init_guard, &cpuinfo_arm_linux_init); + cpuinfo_arm_linux_init(); #elif defined(__MACH__) && defined(__APPLE__) - pthread_once(&init_guard, &cpuinfo_arm_mach_init); + cpuinfo_arm_mach_init(); #elif defined(_WIN32) - InitOnceExecuteOnce(&init_guard, &cpuinfo_arm_windows_init, NULL, NULL); + cpuinfo_arm_windows_init(NULL, NULL, NULL); #else - cpuinfo_log_error("operating system is not supported in cpuinfo"); + cpuinfo_log_error("operating system is not supported in cpuinfo"); #endif #elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 #if defined(__linux__) - pthread_once(&init_guard, &cpuinfo_riscv_linux_init); + cpuinfo_riscv_linux_init(); #else - cpuinfo_log_error("operating system is not supported in cpuinfo"); + cpuinfo_log_error("operating system is not supported in cpuinfo"); #endif #elif CPUINFO_ARCH_ASMJS || CPUINFO_ARCH_WASM || CPUINFO_ARCH_WASMSIMD -#if defined(__EMSCRIPTEN_PTHREADS__) - pthread_once(&init_guard, &cpuinfo_emscripten_init); -#else - if (!init_guard) { cpuinfo_emscripten_init(); - } - init_guard = true; -#endif #else - cpuinfo_log_error("processor architecture is not supported in cpuinfo"); + cpuinfo_log_error("processor architecture is not supported in cpuinfo"); #endif - return cpuinfo_is_initialized; + } + if (cpuinfo_is_initialized) { + cpuinfo_init_refcount++; + } + const bool initialized = cpuinfo_is_initialized; + CPUINFO_LIFECYCLE_UNLOCK(); + return initialized; } void CPUINFO_ABI cpuinfo_deinitialize(void) { - if (!cpuinfo_is_initialized) { - return; - } - cpuinfo_is_initialized = false; + CPUINFO_LIFECYCLE_LOCK(); + if (cpuinfo_init_refcount != 0 && --cpuinfo_init_refcount == 0) { + cpuinfo_is_initialized = false; #if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64 #if defined(__MACH__) && defined(__APPLE__) - pthread_once(&deinit_guard, &cpuinfo_x86_mach_deinit); + cpuinfo_x86_mach_deinit(); #elif defined(__FreeBSD__) - pthread_once(&deinit_guard, &cpuinfo_x86_freebsd_deinit); + cpuinfo_x86_freebsd_deinit(); #elif defined(__linux__) - pthread_once(&deinit_guard, &cpuinfo_x86_linux_deinit); + cpuinfo_x86_linux_deinit(); #elif defined(_WIN32) || defined(__CYGWIN__) - InitOnceExecuteOnce(&deinit_guard, &cpuinfo_x86_windows_deinit, NULL, NULL); + cpuinfo_x86_windows_deinit(NULL, NULL, NULL); #endif #elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 #if defined(__linux__) - pthread_once(&deinit_guard, &cpuinfo_arm_linux_deinit); + cpuinfo_arm_linux_deinit(); #elif defined(__MACH__) && defined(__APPLE__) - pthread_once(&deinit_guard, &cpuinfo_arm_mach_deinit); + cpuinfo_arm_mach_deinit(); #elif defined(_WIN32) - InitOnceExecuteOnce(&deinit_guard, &cpuinfo_arm_windows_deinit, NULL, NULL); + cpuinfo_arm_windows_deinit(NULL, NULL, NULL); #endif #elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 #if defined(__linux__) - pthread_once(&deinit_guard, &cpuinfo_riscv_linux_deinit); + cpuinfo_riscv_linux_deinit(); #endif #elif CPUINFO_ARCH_ASMJS || CPUINFO_ARCH_WASM || CPUINFO_ARCH_WASMSIMD -#if defined(__EMSCRIPTEN_PTHREADS__) - pthread_once(&deinit_guard, &cpuinfo_emscripten_deinit); -#else - if (!deinit_guard) { cpuinfo_emscripten_deinit(); - } - deinit_guard = true; -#endif -#endif - /* Reset guards to allow re-initialization */ -#if defined(_WIN32) || defined(__CYGWIN__) - init_guard = (INIT_ONCE)INIT_ONCE_STATIC_INIT; - deinit_guard = (INIT_ONCE)INIT_ONCE_STATIC_INIT; -#elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__) - init_guard = (pthread_once_t)PTHREAD_ONCE_INIT; - deinit_guard = (pthread_once_t)PTHREAD_ONCE_INIT; -#else - init_guard = false; - deinit_guard = false; #endif + } + CPUINFO_LIFECYCLE_UNLOCK(); } diff --git a/test/init.cc b/test/init.cc index a6128e35..f324521a 100644 --- a/test/init.cc +++ b/test/init.cc @@ -1,3 +1,7 @@ +#include +#include +#include + #include #include @@ -1519,3 +1523,56 @@ TEST(L4_CACHE, consistent_processors) { } cpuinfo_deinitialize(); } + +TEST(INIT_REFCOUNT, deinitialize_balances_initialize) { + ASSERT_TRUE(cpuinfo_initialize()); + ASSERT_TRUE(cpuinfo_initialize()); + + const uint32_t processors_count = cpuinfo_get_processors_count(); + EXPECT_NE(0, processors_count); + + cpuinfo_deinitialize(); + EXPECT_EQ(processors_count, cpuinfo_get_processors_count()); + EXPECT_TRUE(cpuinfo_get_processors()); + + cpuinfo_deinitialize(); +} + +TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { + constexpr int kChurnThreads = 10; + constexpr int kChurnIterations = 5000; + + std::atomic stop_holder{false}; + + // Models a long lived consumer that keeps using cpuinfo for its whole lifetime + // If a concurrent deinitialize from another consumer (churn) frees the shared state + // then cpuinfo aborts the process, which fails the test + const auto holder = [&stop_holder]() { + cpuinfo_initialize(); + while (!stop_holder.load(std::memory_order_relaxed)) { + (void) cpuinfo_get_processors(); + (void) cpuinfo_get_processor(0); + } + cpuinfo_deinitialize(); + }; + const auto churn = [kChurnIterations]() { + for (int iteration = 0; iteration < kChurnIterations; iteration++) { + cpuinfo_initialize(); + cpuinfo_deinitialize(); + } + }; + + std::thread holder_thread(holder); + std::vector churn_threads; + churn_threads.reserve(kChurnThreads); + for (int t = 0; t < kChurnThreads; t++) { + churn_threads.emplace_back(churn); + } + for (std::thread& thread : churn_threads) { + thread.join(); + } + stop_holder.store(true, std::memory_order_relaxed); + holder_thread.join(); + + // Reaching here without the process aborting means that lifecycle handling is correct +} From e16a4200a80976656a6f5283bd8e07b838db4d38 Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Tue, 30 Jun 2026 11:05:48 -0700 Subject: [PATCH 2/3] Add gating for the deinit path --- .github/workflows/build.yml | 14 ++++++++ CMakeLists.txt | 4 +++ src/init.c | 64 +++++++++++++++++++++++++++++++++++++ test/init.cc | 16 +++++++--- 4 files changed, 94 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 13942231..1c84830a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,6 +45,20 @@ jobs: run: scripts/local-build.sh shell: bash # Specify bash so we can reuse the build script on Windows (runs on Git bash) working-directory: ${{ github.workspace }} + cmake-windows-deinit-tests: + runs-on: windows-latest + timeout-minutes: 40 + steps: + - uses: actions/checkout@v4 + - name: Install ninja + run: choco install ninja + - name: Configure and build with deinit enabled + run: scripts/local-build.sh -DCPUINFO_ENABLE_DEINIT=ON + shell: bash + working-directory: ${{ github.workspace }} + - name: Run init-test + run: ctest --output-on-failure -R init-test + working-directory: ${{ github.workspace }}/build/local cmake-uwp: runs-on: windows-latest timeout-minutes: 40 diff --git a/CMakeLists.txt b/CMakeLists.txt index 072c9873..bf1476b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ OPTION(CPUINFO_BUILD_UNIT_TESTS "Build cpuinfo unit tests" ON) OPTION(CPUINFO_BUILD_MOCK_TESTS "Build cpuinfo mock tests" ON) OPTION(CPUINFO_BUILD_BENCHMARKS "Build cpuinfo micro-benchmarks" ON) OPTION(CPUINFO_BUILD_PKG_CONFIG "Build pkg-config manifest" ON) +OPTION(CPUINFO_ENABLE_DEINIT "Release cpuinfo global state on cpuinfo_deinitialize()" OFF) OPTION(USE_SYSTEM_LIBS "Use system libraries instead of downloading and building them" OFF) OPTION(USE_SYSTEM_GOOGLEBENCHMARK "Use system Google Benchmark library instead of downloading and building it" ${USE_SYSTEM_LIBS}) OPTION(USE_SYSTEM_GOOGLETEST "Use system Google Test library instead of downloading and building it" ${USE_SYSTEM_LIBS}) @@ -281,6 +282,8 @@ TARGET_INCLUDE_DIRECTORIES(cpuinfo BEFORE PUBLIC $") +TARGET_COMPILE_DEFINITIONS(cpuinfo PRIVATE "CPUINFO_ENABLE_DEINIT=$") +TARGET_COMPILE_DEFINITIONS(cpuinfo_internals PRIVATE "CPUINFO_ENABLE_DEINIT=$") IF(CPUINFO_LOG_LEVEL STREQUAL "default") # default logging level: error (subject to change) TARGET_COMPILE_DEFINITIONS(cpuinfo PRIVATE "CPUINFO_LOG_LEVEL=2") @@ -812,6 +815,7 @@ IF(CPUINFO_SUPPORTED_PLATFORM AND CPUINFO_BUILD_UNIT_TESTS) ADD_EXECUTABLE(init-test test/init.cc) CPUINFO_TARGET_ENABLE_CXX11(init-test) CPUINFO_TARGET_RUNTIME_LIBRARY(init-test) + TARGET_COMPILE_DEFINITIONS(init-test PRIVATE "CPUINFO_ENABLE_DEINIT=$") TARGET_LINK_LIBRARIES(init-test PRIVATE cpuinfo gtest gtest_main) ADD_TEST(NAME init-test COMMAND init-test) diff --git a/src/init.c b/src/init.c index e3cd9217..483ee9d7 100644 --- a/src/init.c +++ b/src/init.c @@ -12,6 +12,68 @@ #include "TargetConditionals.h" #endif +#ifndef CPUINFO_ENABLE_DEINIT +#define CPUINFO_ENABLE_DEINIT 0 +#endif + +#if !CPUINFO_ENABLE_DEINIT + +#if defined(_WIN32) || defined(__CYGWIN__) +static INIT_ONCE init_guard = INIT_ONCE_STATIC_INIT; +#elif !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__) +static pthread_once_t init_guard = PTHREAD_ONCE_INIT; +#else +static bool init_guard = false; +#endif + +bool CPUINFO_ABI cpuinfo_initialize(void) { +#if CPUINFO_ARCH_X86 || CPUINFO_ARCH_X86_64 +#if defined(__MACH__) && defined(__APPLE__) + pthread_once(&init_guard, &cpuinfo_x86_mach_init); +#elif defined(__FreeBSD__) + pthread_once(&init_guard, &cpuinfo_x86_freebsd_init); +#elif defined(__linux__) + pthread_once(&init_guard, &cpuinfo_x86_linux_init); +#elif defined(_WIN32) || defined(__CYGWIN__) + InitOnceExecuteOnce(&init_guard, &cpuinfo_x86_windows_init, NULL, NULL); +#else + cpuinfo_log_error("operating system is not supported in cpuinfo"); +#endif +#elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 +#if defined(__linux__) + pthread_once(&init_guard, &cpuinfo_arm_linux_init); +#elif defined(__MACH__) && defined(__APPLE__) + pthread_once(&init_guard, &cpuinfo_arm_mach_init); +#elif defined(_WIN32) + InitOnceExecuteOnce(&init_guard, &cpuinfo_arm_windows_init, NULL, NULL); +#else + cpuinfo_log_error("operating system is not supported in cpuinfo"); +#endif +#elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if defined(__linux__) + pthread_once(&init_guard, &cpuinfo_riscv_linux_init); +#else + cpuinfo_log_error("operating system is not supported in cpuinfo"); +#endif +#elif CPUINFO_ARCH_ASMJS || CPUINFO_ARCH_WASM || CPUINFO_ARCH_WASMSIMD +#if defined(__EMSCRIPTEN_PTHREADS__) + pthread_once(&init_guard, &cpuinfo_emscripten_init); +#else + if (!init_guard) { + cpuinfo_emscripten_init(); + } + init_guard = true; +#endif +#else + cpuinfo_log_error("processor architecture is not supported in cpuinfo"); +#endif + return cpuinfo_is_initialized; +} + +void CPUINFO_ABI cpuinfo_deinitialize(void) {} + +#else /* CPUINFO_ENABLE_DEINIT */ + #if defined(_WIN32) || defined(__CYGWIN__) static SRWLOCK cpuinfo_lifecycle_lock = SRWLOCK_INIT; #define CPUINFO_LIFECYCLE_LOCK() AcquireSRWLockExclusive(&cpuinfo_lifecycle_lock) @@ -108,3 +170,5 @@ void CPUINFO_ABI cpuinfo_deinitialize(void) { } CPUINFO_LIFECYCLE_UNLOCK(); } + +#endif /* CPUINFO_ENABLE_DEINIT */ diff --git a/test/init.cc b/test/init.cc index f324521a..6a8f278a 100644 --- a/test/init.cc +++ b/test/init.cc @@ -1,11 +1,17 @@ -#include -#include -#include - #include #include +#ifndef CPUINFO_ENABLE_DEINIT +#define CPUINFO_ENABLE_DEINIT 0 +#endif + +#if CPUINFO_ENABLE_DEINIT +#include +#include +#include +#endif + TEST(PROCESSORS_COUNT, non_zero) { ASSERT_TRUE(cpuinfo_initialize()); EXPECT_NE(0, cpuinfo_get_processors_count()); @@ -1524,6 +1530,7 @@ TEST(L4_CACHE, consistent_processors) { cpuinfo_deinitialize(); } +#if CPUINFO_ENABLE_DEINIT TEST(INIT_REFCOUNT, deinitialize_balances_initialize) { ASSERT_TRUE(cpuinfo_initialize()); ASSERT_TRUE(cpuinfo_initialize()); @@ -1576,3 +1583,4 @@ TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { // Reaching here without the process aborting means that lifecycle handling is correct } +#endif // CPUINFO_ENABLE_DEINIT From 55827f07b36a933a12c7e0519a53bbfe391b1e0e Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Tue, 30 Jun 2026 14:11:56 -0700 Subject: [PATCH 3/3] clang-format fix --- test/init.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/init.cc b/test/init.cc index 6a8f278a..28692a56 100644 --- a/test/init.cc +++ b/test/init.cc @@ -1557,8 +1557,8 @@ TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { const auto holder = [&stop_holder]() { cpuinfo_initialize(); while (!stop_holder.load(std::memory_order_relaxed)) { - (void) cpuinfo_get_processors(); - (void) cpuinfo_get_processor(0); + (void)cpuinfo_get_processors(); + (void)cpuinfo_get_processor(0); } cpuinfo_deinitialize(); }; @@ -1583,4 +1583,4 @@ TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { // Reaching here without the process aborting means that lifecycle handling is correct } -#endif // CPUINFO_ENABLE_DEINIT +#endif // CPUINFO_ENABLE_DEINIT