From c80597fd01491d3493fbe43e0b80972a4c1bf91b Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Mon, 29 Jun 2026 12:40:48 -0700 Subject: [PATCH 1/5] Add mutex and refcounting for init and deinit --- src/init.c | 91 ++++++++++++++++++++++++++++++++++++++-------------- test/init.cc | 57 ++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 24 deletions(-) diff --git a/src/init.c b/src/init.c index 81d5721c..e3cd9217 100644 --- a/src/init.c +++ b/src/init.c @@ -13,55 +13,98 @@ #endif #if defined(_WIN32) || defined(__CYGWIN__) -static INIT_ONCE init_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_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; +/* 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) {} +void CPUINFO_ABI cpuinfo_deinitialize(void) { + 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__) + cpuinfo_x86_mach_deinit(); +#elif defined(__FreeBSD__) + cpuinfo_x86_freebsd_deinit(); +#elif defined(__linux__) + cpuinfo_x86_linux_deinit(); +#elif defined(_WIN32) || defined(__CYGWIN__) + cpuinfo_x86_windows_deinit(NULL, NULL, NULL); +#endif +#elif CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 +#if defined(__linux__) + cpuinfo_arm_linux_deinit(); +#elif defined(__MACH__) && defined(__APPLE__) + cpuinfo_arm_mach_deinit(); +#elif defined(_WIN32) + cpuinfo_arm_windows_deinit(NULL, NULL, NULL); +#endif +#elif CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +#if defined(__linux__) + cpuinfo_riscv_linux_deinit(); +#endif +#elif CPUINFO_ARCH_ASMJS || CPUINFO_ARCH_WASM || CPUINFO_ARCH_WASMSIMD + cpuinfo_emscripten_deinit(); +#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 14ff9f214c1debf224bd1ab687e2f1e522f75bd3 Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Tue, 30 Jun 2026 11:05:48 -0700 Subject: [PATCH 2/5] 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 0d6753c08883808bf02bcf45546be5f47ca5f8c4 Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Tue, 30 Jun 2026 14:11:56 -0700 Subject: [PATCH 3/5] 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 From 3c8fbe43f07b0a2772255d78946c5cbd242981c6 Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Thu, 30 Jul 2026 18:48:19 -0700 Subject: [PATCH 4/5] Implement `cpuinfo_deinitialize()` to free heap-allocated globals --- src/arm/linux/init.c | 40 ++++++++++++++++++++++ src/arm/mach/init.c | 29 ++++++++++++++++ src/arm/windows/init-by-logical-sys-info.c | 34 ++++++++++++++++++ src/arm/windows/init.c | 10 ++++++ src/cpuinfo/internal-api.h | 20 +++++++++++ src/emscripten/init.c | 32 +++++++++++++++++ src/riscv/linux/init.c | 33 ++++++++++++++++++ src/x86/freebsd/init.c | 27 +++++++++++++++ src/x86/linux/init.c | 35 +++++++++++++++++++ src/x86/mach/init.c | 27 +++++++++++++++ src/x86/windows/init.c | 34 ++++++++++++++++++ test/init.cc | 23 +++++++++++-- 12 files changed, 341 insertions(+), 3 deletions(-) diff --git a/src/arm/linux/init.c b/src/arm/linux/init.c index 48c9f4a0..cb3170f2 100644 --- a/src/arm/linux/init.c +++ b/src/arm/linux/init.c @@ -1001,3 +1001,43 @@ void cpuinfo_arm_linux_init(void) { free(linux_cpu_to_core_map); free(linux_cpu_to_uarch_index_map); } + +void cpuinfo_arm_linux_deinit(void) { + free(cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + free(cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + free(cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + /* cpuinfo_packages points to static storage (&package) — do not free */ + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + free(cpuinfo_uarchs); + cpuinfo_uarchs = NULL; + cpuinfo_uarchs_count = 0; + + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + free(cpuinfo_cache[lvl]); + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; + + free(cpuinfo_linux_cpu_to_processor_map); + cpuinfo_linux_cpu_to_processor_map = NULL; + + free(cpuinfo_linux_cpu_to_core_map); + cpuinfo_linux_cpu_to_core_map = NULL; + + free((void*)cpuinfo_linux_cpu_to_uarch_index_map); + cpuinfo_linux_cpu_to_uarch_index_map = NULL; + + cpuinfo_linux_cpu_max = 0; +} diff --git a/src/arm/mach/init.c b/src/arm/mach/init.c index 76224b9d..9a9fb427 100644 --- a/src/arm/mach/init.c +++ b/src/arm/mach/init.c @@ -873,3 +873,32 @@ void cpuinfo_arm_mach_init(void) { free(l2); free(l3); } + +void cpuinfo_arm_mach_deinit(void) { + free(cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + free(cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + free(cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + free(cpuinfo_packages); + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + free(cpuinfo_uarchs); + cpuinfo_uarchs = NULL; + cpuinfo_uarchs_count = 0; + + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + free(cpuinfo_cache[lvl]); + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; +} diff --git a/src/arm/windows/init-by-logical-sys-info.c b/src/arm/windows/init-by-logical-sys-info.c index 815ecb77..2e44f8aa 100644 --- a/src/arm/windows/init-by-logical-sys-info.c +++ b/src/arm/windows/init-by-logical-sys-info.c @@ -372,6 +372,40 @@ bool cpu_info_init_by_logical_sys_info(const struct woa_chip_info* chip_info, co return result; } +BOOL CALLBACK cpuinfo_arm_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context) { + HANDLE heap = GetProcessHeap(); + + HeapFree(heap, 0, cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + HeapFree(heap, 0, cpuinfo_packages); + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + HeapFree(heap, 0, cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + HeapFree(heap, 0, cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + HeapFree(heap, 0, cpuinfo_uarchs); + cpuinfo_uarchs = NULL; + cpuinfo_uarchs_count = 0; + + /* Caches are allocated as a single contiguous block starting at l1i */ + HeapFree(heap, 0, cpuinfo_cache[cpuinfo_cache_level_1i]); + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; + + return TRUE; +} + static uint32_t count_logical_processors(const uint32_t max_group_count, uint32_t* global_proc_index_per_group) { uint32_t nr_of_processors = 0; diff --git a/src/arm/windows/init.c b/src/arm/windows/init.c index 528ae327..fcf219df 100644 --- a/src/arm/windows/init.c +++ b/src/arm/windows/init.c @@ -25,12 +25,18 @@ BOOL CALLBACK cpuinfo_arm_windows_init(PINIT_ONCE init_once, PVOID parameter, PV set_cpuinfo_isa_fields(); chip_info = get_system_info_from_registry(); + const bool chip_info_from_registry = chip_info != NULL; if (chip_info == NULL) { chip_info = &woa_chip_unknown; } cpuinfo_is_initialized = cpu_info_init_by_logical_sys_info(chip_info, chip_info->uarchs[0].vendor); + if (chip_info_from_registry) { + HeapFree(GetProcessHeap(), 0, chip_info->chip_name_string); + HeapFree(GetProcessHeap(), 0, chip_info); + } + return true; } @@ -177,6 +183,7 @@ static struct woa_chip_info* get_system_info_from_registry(void) { uint64_t midr_qword = (uint32_t)read_registry_qword(cpu0_subkey, chip_midr_value); if (midr_qword == 0) { cpuinfo_log_error("Registry read error for MIDR value"); + HeapFree(GetProcessHeap(), 0, text_buffer); return NULL; } // MIDR is only 32 bits, so we need to cast it to uint32_t @@ -187,6 +194,7 @@ static struct woa_chip_info* get_system_info_from_registry(void) { uint64_t frequency_mhz = read_registry_dword(cpu0_subkey, chip_mhz_value); if (frequency_mhz == 0) { cpuinfo_log_error("Registry read error for frequency value"); + HeapFree(GetProcessHeap(), 0, text_buffer); return NULL; } // Convert MHz to Hz @@ -196,11 +204,13 @@ static struct woa_chip_info* get_system_info_from_registry(void) { chip_info = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(struct woa_chip_info)); if (chip_info == NULL) { cpuinfo_log_error("Heap allocation error for chip_info"); + HeapFree(GetProcessHeap(), 0, text_buffer); return NULL; } // set chip_info fields chip_info->chip_name_string = wcsndup(text_buffer, CPUINFO_PACKAGE_NAME_MAX - 1); + HeapFree(GetProcessHeap(), 0, text_buffer); chip_info->uarchs[0] = get_core_info_from_midr(midr_value, frequency_hz); cpuinfo_log_debug("detected chip model name: %ls", chip_info->chip_name_string); diff --git a/src/cpuinfo/internal-api.h b/src/cpuinfo/internal-api.h index d84b26a8..9c158887 100644 --- a/src/cpuinfo/internal-api.h +++ b/src/cpuinfo/internal-api.h @@ -45,6 +45,9 @@ extern CPUINFO_INTERNAL struct cpuinfo_uarch_info cpuinfo_global_uarch; extern CPUINFO_INTERNAL uint32_t cpuinfo_linux_cpu_max; extern CPUINFO_INTERNAL const struct cpuinfo_processor** cpuinfo_linux_cpu_to_processor_map; extern CPUINFO_INTERNAL const struct cpuinfo_core** cpuinfo_linux_cpu_to_core_map; +#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64 || CPUINFO_ARCH_RISCV32 || CPUINFO_ARCH_RISCV64 +extern CPUINFO_INTERNAL const uint32_t* cpuinfo_linux_cpu_to_uarch_index_map; +#endif #endif CPUINFO_PRIVATE void cpuinfo_x86_mach_init(void); @@ -62,6 +65,23 @@ CPUINFO_PRIVATE void cpuinfo_arm_linux_init(void); CPUINFO_PRIVATE void cpuinfo_riscv_linux_init(void); CPUINFO_PRIVATE void cpuinfo_emscripten_init(void); +/* Platform-specific deinitialization functions. + * Each frees the globals allocated by its corresponding init function. */ +CPUINFO_PRIVATE void cpuinfo_x86_mach_deinit(void); +CPUINFO_PRIVATE void cpuinfo_x86_linux_deinit(void); +CPUINFO_PRIVATE void cpuinfo_x86_freebsd_deinit(void); +#if defined(_WIN32) || defined(__CYGWIN__) +#if CPUINFO_ARCH_ARM64 +CPUINFO_PRIVATE BOOL CALLBACK cpuinfo_arm_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context); +#else +CPUINFO_PRIVATE BOOL CALLBACK cpuinfo_x86_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context); +#endif +#endif +CPUINFO_PRIVATE void cpuinfo_arm_mach_deinit(void); +CPUINFO_PRIVATE void cpuinfo_arm_linux_deinit(void); +CPUINFO_PRIVATE void cpuinfo_riscv_linux_deinit(void); +CPUINFO_PRIVATE void cpuinfo_emscripten_deinit(void); + CPUINFO_PRIVATE uint32_t cpuinfo_compute_max_cache_size(const struct cpuinfo_processor* processor); typedef void (*cpuinfo_processor_callback)(uint32_t); diff --git a/src/emscripten/init.c b/src/emscripten/init.c index c2393243..6c8210f2 100644 --- a/src/emscripten/init.c +++ b/src/emscripten/init.c @@ -286,3 +286,35 @@ void cpuinfo_emscripten_init(void) { free(l1d); free(l2); } + +void cpuinfo_emscripten_deinit(void) { + free(cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + free(cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + free(cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + /* cpuinfo_packages points to static storage (&static_package) — do not free */ + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + /* L1i, L1d, L2 are dynamically allocated; L3 may point to static + * storage (&static_x86_l3) so only free levels that were calloc'd */ + free(cpuinfo_cache[cpuinfo_cache_level_1i]); + free(cpuinfo_cache[cpuinfo_cache_level_1d]); + free(cpuinfo_cache[cpuinfo_cache_level_2]); + /* cpuinfo_cache[cpuinfo_cache_level_3] may be &static_x86_l3 — do not free */ + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; + + cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0}; +} diff --git a/src/riscv/linux/init.c b/src/riscv/linux/init.c index 45168c7e..3da8c3ff 100644 --- a/src/riscv/linux/init.c +++ b/src/riscv/linux/init.c @@ -618,3 +618,36 @@ void cpuinfo_riscv_linux_init(void) { free(linux_cpu_to_core_map); free(linux_cpu_to_uarch_index_map); } + +void cpuinfo_riscv_linux_deinit(void) { + free(cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + free(cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + free(cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + free(cpuinfo_packages); + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + free(cpuinfo_uarchs); + cpuinfo_uarchs = NULL; + cpuinfo_uarchs_count = 0; + + free(cpuinfo_linux_cpu_to_processor_map); + cpuinfo_linux_cpu_to_processor_map = NULL; + + free(cpuinfo_linux_cpu_to_core_map); + cpuinfo_linux_cpu_to_core_map = NULL; + + free((void*)cpuinfo_linux_cpu_to_uarch_index_map); + cpuinfo_linux_cpu_to_uarch_index_map = NULL; + + cpuinfo_linux_cpu_max = 0; +} diff --git a/src/x86/freebsd/init.c b/src/x86/freebsd/init.c index 797fa24b..ba30a5d9 100644 --- a/src/x86/freebsd/init.c +++ b/src/x86/freebsd/init.c @@ -396,3 +396,30 @@ void cpuinfo_x86_freebsd_init(void) { free(l3); free(l4); } + +void cpuinfo_x86_freebsd_deinit(void) { + free(cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + free(cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + free(cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + free(cpuinfo_packages); + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + free(cpuinfo_cache[lvl]); + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; + + cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0}; +} diff --git a/src/x86/linux/init.c b/src/x86/linux/init.c index d2b2d475..62e1187e 100644 --- a/src/x86/linux/init.c +++ b/src/x86/linux/init.c @@ -676,3 +676,38 @@ void cpuinfo_x86_linux_init(void) { free(linux_cpu_to_processor_map); free(linux_cpu_to_core_map); } + +void cpuinfo_x86_linux_deinit(void) { + free(cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + free(cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + free(cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + free(cpuinfo_packages); + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + free(cpuinfo_cache[lvl]); + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; + + free(cpuinfo_linux_cpu_to_processor_map); + cpuinfo_linux_cpu_to_processor_map = NULL; + + free(cpuinfo_linux_cpu_to_core_map); + cpuinfo_linux_cpu_to_core_map = NULL; + + cpuinfo_linux_cpu_max = 0; + + cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0}; +} diff --git a/src/x86/mach/init.c b/src/x86/mach/init.c index b8ea6047..9009e830 100644 --- a/src/x86/mach/init.c +++ b/src/x86/mach/init.c @@ -378,3 +378,30 @@ void cpuinfo_x86_mach_init(void) { free(l3); free(l4); } + +void cpuinfo_x86_mach_deinit(void) { + free(cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + free(cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + free(cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + free(cpuinfo_packages); + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + free(cpuinfo_cache[lvl]); + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; + + cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0}; +} diff --git a/src/x86/windows/init.c b/src/x86/windows/init.c index 7380bb46..f1401beb 100644 --- a/src/x86/windows/init.c +++ b/src/x86/windows/init.c @@ -665,5 +665,39 @@ BOOL CALLBACK cpuinfo_x86_windows_init(PINIT_ONCE init_once, PVOID parameter, PV if (l4 != NULL) { HeapFree(heap, 0, l4); } + if (processor_infos != NULL) { + HeapFree(heap, 0, processor_infos); + } + return TRUE; +} + +BOOL CALLBACK cpuinfo_x86_windows_deinit(PINIT_ONCE init_once, PVOID parameter, PVOID* context) { + HANDLE heap = GetProcessHeap(); + + HeapFree(heap, 0, cpuinfo_processors); + cpuinfo_processors = NULL; + cpuinfo_processors_count = 0; + + HeapFree(heap, 0, cpuinfo_cores); + cpuinfo_cores = NULL; + cpuinfo_cores_count = 0; + + HeapFree(heap, 0, cpuinfo_clusters); + cpuinfo_clusters = NULL; + cpuinfo_clusters_count = 0; + + HeapFree(heap, 0, cpuinfo_packages); + cpuinfo_packages = NULL; + cpuinfo_packages_count = 0; + + for (int lvl = 0; lvl < cpuinfo_cache_level_max; ++lvl) { + HeapFree(heap, 0, cpuinfo_cache[lvl]); + cpuinfo_cache[lvl] = NULL; + cpuinfo_cache_count[lvl] = 0; + } + cpuinfo_max_cache_size = 0; + + cpuinfo_global_uarch = (struct cpuinfo_uarch_info){0}; + return TRUE; } diff --git a/test/init.cc b/test/init.cc index 28692a56..4ffdb13e 100644 --- a/test/init.cc +++ b/test/init.cc @@ -1550,12 +1550,18 @@ TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { constexpr int kChurnIterations = 5000; std::atomic stop_holder{false}; + std::atomic holder_started{false}; + std::atomic holder_initialized{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(); + const auto holder = [&]() { + holder_initialized.store(cpuinfo_initialize(), std::memory_order_relaxed); + holder_started.store(true, std::memory_order_release); + if (!holder_initialized.load(std::memory_order_relaxed)) { + return; + } while (!stop_holder.load(std::memory_order_relaxed)) { (void)cpuinfo_get_processors(); (void)cpuinfo_get_processor(0); @@ -1570,6 +1576,14 @@ TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { }; std::thread holder_thread(holder); + while (!holder_started.load(std::memory_order_acquire)) { + std::this_thread::yield(); + } + if (!holder_initialized.load(std::memory_order_relaxed)) { + holder_thread.join(); + FAIL() << "holder failed to initialize cpuinfo"; + } + std::vector churn_threads; churn_threads.reserve(kChurnThreads); for (int t = 0; t < kChurnThreads; t++) { @@ -1581,6 +1595,9 @@ TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { stop_holder.store(true, std::memory_order_relaxed); holder_thread.join(); - // Reaching here without the process aborting means that lifecycle handling is correct + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_NE(0, cpuinfo_get_processors_count()); + EXPECT_TRUE(cpuinfo_get_processors()); + cpuinfo_deinitialize(); } #endif // CPUINFO_ENABLE_DEINIT From ac869cf35059ae80472ff3ca166a95ce2f089289 Mon Sep 17 00:00:00 2001 From: Vineeth Chelur Date: Thu, 30 Jul 2026 19:40:34 -0700 Subject: [PATCH 5/5] Remove optional build flag --- .github/workflows/build.yml | 20 ++-- CMakeLists.txt | 4 - src/init.c | 64 ------------ test/init.cc | 199 +++++++++++++++++++++++++++++------- 4 files changed, 167 insertions(+), 120 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c84830a..50099857 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,8 @@ jobs: - name: Configure and build run: scripts/local-build.sh working-directory: ${{ github.workspace }} + - name: Run tests + run: ctest --test-dir build/local --output-on-failure cmake-darwin: runs-on: macos-latest timeout-minutes: 40 @@ -34,6 +36,8 @@ jobs: - name: Configure and build run: scripts/local-build.sh working-directory: ${{ github.workspace }} + - name: Run tests + run: ctest --test-dir build/local --output-on-failure cmake-windows: runs-on: windows-latest timeout-minutes: 40 @@ -45,20 +49,8 @@ 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 + - name: Run tests + run: ctest --test-dir build/local --output-on-failure cmake-uwp: runs-on: windows-latest timeout-minutes: 40 diff --git a/CMakeLists.txt b/CMakeLists.txt index bf1476b7..072c9873 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,6 @@ 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}) @@ -282,8 +281,6 @@ 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") @@ -815,7 +812,6 @@ 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 483ee9d7..e3cd9217 100644 --- a/src/init.c +++ b/src/init.c @@ -12,68 +12,6 @@ #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) @@ -170,5 +108,3 @@ void CPUINFO_ABI cpuinfo_deinitialize(void) { } CPUINFO_LIFECYCLE_UNLOCK(); } - -#endif /* CPUINFO_ENABLE_DEINIT */ diff --git a/test/init.cc b/test/init.cc index 4ffdb13e..899563bb 100644 --- a/test/init.cc +++ b/test/init.cc @@ -2,15 +2,47 @@ #include -#ifndef CPUINFO_ENABLE_DEINIT -#define CPUINFO_ENABLE_DEINIT 0 -#endif - -#if CPUINFO_ENABLE_DEINIT #include +#include +#include +#include #include #include -#endif + +namespace { + +class ThreadBarrier { + public: + explicit ThreadBarrier(size_t participant_count) + : participant_count_(participant_count), remaining_(participant_count), generation_(0) {} + + void Wait() { + std::unique_lock lock(mutex_); + const size_t generation = generation_; + if (--remaining_ == 0) { + generation_++; + remaining_ = participant_count_; + condition_.notify_all(); + return; + } + + condition_.wait(lock, [this, generation]() { return generation_ != generation; }); + } + + private: + const size_t participant_count_; + size_t remaining_; + size_t generation_; + std::mutex mutex_; + std::condition_variable condition_; +}; + +bool HasValidCpuinfoState() { + return cpuinfo_get_processors_count() != 0 && cpuinfo_get_processors() != nullptr && + cpuinfo_get_processor(0) != nullptr; +} + +} // namespace TEST(PROCESSORS_COUNT, non_zero) { ASSERT_TRUE(cpuinfo_initialize()); @@ -1530,7 +1562,6 @@ 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()); @@ -1545,59 +1576,151 @@ TEST(INIT_REFCOUNT, deinitialize_balances_initialize) { cpuinfo_deinitialize(); } -TEST(INIT_STRESS, concurrent_deinitialize_does_not_disturb_other_consumers) { - constexpr int kChurnThreads = 10; - constexpr int kChurnIterations = 5000; +TEST(INIT_REFCOUNT, extra_deinitialize_does_not_underflow) { + cpuinfo_deinitialize(); + cpuinfo_deinitialize(); + + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); + cpuinfo_deinitialize(); + + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); +} + +TEST(INIT_REINITIALIZE, repeated_cycles_restore_valid_state) { + constexpr size_t kCycles = 25; + + for (size_t cycle = 0; cycle < kCycles; cycle++) { + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); + } +} + +TEST(INIT_CONCURRENCY, simultaneous_consumers_share_lifecycle) { + constexpr size_t kThreadCount = 12; + constexpr size_t kRounds = 25; + + ThreadBarrier initialized_barrier(kThreadCount); + ThreadBarrier deinitialized_barrier(kThreadCount); + std::atomic failures{0}; + + const auto consumer = [&]() { + for (size_t round = 0; round < kRounds; round++) { + const bool initialized = cpuinfo_initialize(); + if (!initialized) { + failures.fetch_add(1, std::memory_order_relaxed); + } + + initialized_barrier.Wait(); + if (initialized && !HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + } + if (initialized) { + cpuinfo_deinitialize(); + } + deinitialized_barrier.Wait(); + } + }; + + std::vector consumers; + consumers.reserve(kThreadCount); + for (size_t thread = 0; thread < kThreadCount; thread++) { + consumers.emplace_back(consumer); + } + for (std::thread& consumer_thread : consumers) { + consumer_thread.join(); + } + + EXPECT_EQ(0u, failures.load(std::memory_order_relaxed)); + ASSERT_TRUE(cpuinfo_initialize()); + EXPECT_TRUE(HasValidCpuinfoState()); + cpuinfo_deinitialize(); +} + +TEST(INIT_CONCURRENCY, long_lived_consumers_survive_concurrent_churn) { + constexpr size_t kHolderThreadCount = 4; + constexpr size_t kChurnThreadCount = 8; + constexpr size_t kChurnIterations = 2000; - std::atomic stop_holder{false}; - std::atomic holder_started{false}; - std::atomic holder_initialized{false}; + std::atomic stop_holders{false}; + std::atomic holders_ready{0}; + std::atomic holder_references{0}; + std::atomic failures{0}; - // 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 = [&]() { - holder_initialized.store(cpuinfo_initialize(), std::memory_order_relaxed); - holder_started.store(true, std::memory_order_release); - if (!holder_initialized.load(std::memory_order_relaxed)) { + const bool initialized = cpuinfo_initialize(); + if (initialized) { + holder_references.fetch_add(1, std::memory_order_relaxed); + if (!HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + } + } else { + failures.fetch_add(1, std::memory_order_relaxed); + } + holders_ready.fetch_add(1, std::memory_order_release); + + if (!initialized) { return; } - while (!stop_holder.load(std::memory_order_relaxed)) { - (void)cpuinfo_get_processors(); - (void)cpuinfo_get_processor(0); + + while (!stop_holders.load(std::memory_order_acquire)) { + if (!HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + break; + } + std::this_thread::yield(); } + cpuinfo_deinitialize(); }; - const auto churn = [kChurnIterations]() { - for (int iteration = 0; iteration < kChurnIterations; iteration++) { - cpuinfo_initialize(); + const auto churn = [&]() { + for (size_t iteration = 0; iteration < kChurnIterations; iteration++) { + if (!cpuinfo_initialize()) { + failures.fetch_add(1, std::memory_order_relaxed); + continue; + } + if (!HasValidCpuinfoState()) { + failures.fetch_add(1, std::memory_order_relaxed); + } cpuinfo_deinitialize(); } }; - std::thread holder_thread(holder); - while (!holder_started.load(std::memory_order_acquire)) { - std::this_thread::yield(); + std::vector holder_threads; + holder_threads.reserve(kHolderThreadCount); + for (size_t thread = 0; thread < kHolderThreadCount; thread++) { + holder_threads.emplace_back(holder); } - if (!holder_initialized.load(std::memory_order_relaxed)) { - holder_thread.join(); - FAIL() << "holder failed to initialize cpuinfo"; + while (holders_ready.load(std::memory_order_acquire) != kHolderThreadCount) { + std::this_thread::yield(); } std::vector churn_threads; - churn_threads.reserve(kChurnThreads); - for (int t = 0; t < kChurnThreads; t++) { + churn_threads.reserve(kChurnThreadCount); + for (size_t thread = 0; thread < kChurnThreadCount; thread++) { churn_threads.emplace_back(churn); } for (std::thread& thread : churn_threads) { thread.join(); } - stop_holder.store(true, std::memory_order_relaxed); - holder_thread.join(); + + EXPECT_EQ(kHolderThreadCount, holder_references.load(std::memory_order_relaxed)); + EXPECT_EQ(0u, failures.load(std::memory_order_relaxed)); + if (holder_references.load(std::memory_order_relaxed) != 0) { + EXPECT_TRUE(HasValidCpuinfoState()); + } + + stop_holders.store(true, std::memory_order_release); + for (std::thread& thread : holder_threads) { + thread.join(); + } + EXPECT_EQ(0u, failures.load(std::memory_order_relaxed)); ASSERT_TRUE(cpuinfo_initialize()); - EXPECT_NE(0, cpuinfo_get_processors_count()); - EXPECT_TRUE(cpuinfo_get_processors()); + EXPECT_TRUE(HasValidCpuinfoState()); cpuinfo_deinitialize(); } -#endif // CPUINFO_ENABLE_DEINIT