From c2775cc11ce7a8bae8240b4320690bbb730397d7 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Tue, 21 Oct 2025 11:58:53 -0500 Subject: [PATCH 01/15] Add re-entrant qsort detection --- config/ConfigureChecks.cmake | 9 +++++++++ src/H5pubconf.h.in | 3 +++ 2 files changed, 12 insertions(+) diff --git a/config/ConfigureChecks.cmake b/config/ConfigureChecks.cmake index 72b9552f7a0..04918411e30 100644 --- a/config/ConfigureChecks.cmake +++ b/config/ConfigureChecks.cmake @@ -443,6 +443,15 @@ CHECK_FUNCTION_EXISTS (asprintf ${HDF_PREFIX}_HAVE_ASPRINTF) CHECK_FUNCTION_EXISTS (vasprintf ${HDF_PREFIX}_HAVE_VASPRINTF) CHECK_FUNCTION_EXISTS (waitpid ${HDF_PREFIX}_HAVE_WAITPID) +# Check for reentrant qsort variants (qsort_r on Unix/BSD, qsort_s on Windows) +# Note: qsort_r has different signatures on GNU/Linux vs BSD/macOS, but we just +# check for existence here. Signature differences will be handled in the code. +CHECK_FUNCTION_EXISTS (qsort_r _HAVE_QSORT_R_TMP) +CHECK_FUNCTION_EXISTS (qsort_s _HAVE_QSORT_S_TMP) +if (_HAVE_QSORT_R_TMP OR _HAVE_QSORT_S_TMP) + set (${HDF_PREFIX}_HAVE_QSORT_REENTRANT 1) +endif () + #----------------------------------------------------------------------------- # sigsetjmp is special; may actually be a macro #----------------------------------------------------------------------------- diff --git a/src/H5pubconf.h.in b/src/H5pubconf.h.in index 93b28b5327f..82fc2f5f804 100644 --- a/src/H5pubconf.h.in +++ b/src/H5pubconf.h.in @@ -258,6 +258,9 @@ /* Define to 1 if you have the header file. */ #cmakedefine H5_HAVE_PWD_H @H5_HAVE_PWD_H@ +/* Define to 1 if you have a reentrant qsort function (qsort_r or qsort_s). */ +#cmakedefine H5_HAVE_QSORT_REENTRANT @H5_HAVE_QSORT_REENTRANT@ + /* Define whether the Read-Only S3 virtual file driver (VFD) should be compiled */ #cmakedefine H5_HAVE_ROS3_VFD @H5_HAVE_ROS3_VFD@ From 2ef412f1c4d120cca0933f6b8f0f001852fcf71f Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Tue, 21 Oct 2025 12:15:33 -0500 Subject: [PATCH 02/15] Implement fallback for re-entrant qsort --- src/H5private.h | 10 ++++++ src/H5system.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/src/H5private.h b/src/H5private.h index 19e02e14e50..e89a45cf49e 100644 --- a/src/H5private.h +++ b/src/H5private.h @@ -654,6 +654,11 @@ H5_DLL void HDqsort_context(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), void *arg); #endif +#ifndef H5_HAVE_QSORT_REENTRANT +H5_DLL void HDqsort_fallback(void *base, size_t nel, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); +#endif + #ifndef HDfseek #define HDfseek(F, O, W) fseeko(F, O, W) #endif @@ -770,11 +775,16 @@ H5_DLL void HDqsort_context(void *base, size_t nel, size_t size, #define HDunsetenv(S) unsetenv(S) #endif #ifndef HDqsort_r +#ifdef H5_HAVE_QSORT_REENTRANT #ifdef H5_HAVE_DARWIN #define HDqsort_r(B, N, S, C, A) HDqsort_context(B, N, S, C, A) #else #define HDqsort_r(B, N, S, C, A) qsort_r(B, N, S, C, A) #endif +#else +/* No native qsort_r/qsort_s available - use fallback implementation */ +#define HDqsort_r(B, N, S, C, A) HDqsort_fallback(B, N, S, C, A) +#endif #endif #ifndef HDvasprintf #ifdef H5_HAVE_VASPRINTF diff --git a/src/H5system.c b/src/H5system.c index 8095b304aed..e78ec360e34 100644 --- a/src/H5system.c +++ b/src/H5system.c @@ -1456,3 +1456,99 @@ HDqsort_context(void *base, size_t nel, size_t size, int (*compar)(const void *, #endif } #endif + +/* + * HDqsort_fallback - Fallback qsort implementation for platforms without qsort_r/qsort_s + * + * For platforms that don't provide any reentrant qsort variant, this fallback uses + * thread-local storage (when thread-safety is + * enabled) or a global variable to store the comparator context, then uses standard qsort(). + * + * The threadsafe version uses thread-local storage but does not support recursive sorting (a + * comparator calling HDqsort_r) as it would overwrite the previous context. + */ +#ifndef H5_HAVE_QSORT_REENTRANT + +typedef struct HDqsort_fallback_context_t { + int (*gnu_compar)(const void *, const void *, void *); + void *gnu_arg; +} HDqsort_fallback_context_t; + +#ifdef H5_HAVE_THREADSAFE +/* Thread-local storage approach for thread-safe builds */ +static H5TS_key_t HDqsort_fallback_key; +static H5TS_once_t HDqsort_fallback_key_once = H5TS_ONCE_INITIALIZER; + +static void +HDqsort_fallback_key_init(void) +{ + /* Create the thread-local storage key (no destructor needed) */ + H5TS_key_create(&HDqsort_fallback_key, NULL); +} + +static int +HDqsort_fallback_wrapper(const void *a, const void *b) +{ + HDqsort_fallback_context_t *ctx = NULL; + + /* Retrieve the context from thread-local storage + * This should never fail since we just set it in HDqsort_fallback() */ + if (H5_UNLIKELY(H5TS_key_get_value(HDqsort_fallback_key, (void **)&ctx) < 0)) + return 0; /* Should never happen, but return 0 (equal) if it does */ + + /* Call the original GNU-style comparator with context */ + return ctx->gnu_compar(a, b, ctx->gnu_arg); +} + +void +HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), + void *arg) +{ + HDqsort_fallback_context_t ctx; + + /* Ensure the TLS key is initialized */ + H5TS_once(&HDqsort_fallback_key_once, HDqsort_fallback_key_init); + + ctx.gnu_compar = compar; + ctx.gnu_arg = arg; + + /* Store context in thread-local storage */ + H5TS_key_set_value(HDqsort_fallback_key, &ctx); + + qsort(base, nel, size, HDqsort_fallback_wrapper); + + /* Clear the thread-local storage */ + H5TS_key_set_value(HDqsort_fallback_key, NULL); +} + +#else +/* Non-threadsafe: use global variable */ +static HDqsort_fallback_context_t *HDqsort_fallback_global_ctx = NULL; + +static int +HDqsort_fallback_wrapper(const void *a, const void *b) +{ + /* Call the original GNU-style comparator with context from global */ + return HDqsort_fallback_global_ctx->gnu_compar(a, b, HDqsort_fallback_global_ctx->gnu_arg); +} + +void +HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), + void *arg) +{ + HDqsort_fallback_context_t ctx; + + ctx.gnu_compar = compar; + ctx.gnu_arg = arg; + + /* Store context in global variable */ + HDqsort_fallback_global_ctx = &ctx; + + qsort(base, nel, size, HDqsort_fallback_wrapper); + + /* Clear the global pointer */ + HDqsort_fallback_global_ctx = NULL; +} +#endif /* H5_HAVE_THREADSAFE */ + +#endif /* !H5_HAVE_QSORT_REENTRANT */ From 601012d878abf07945649a4b31e9ce4bfc47ae5a Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Tue, 21 Oct 2025 12:33:58 -0500 Subject: [PATCH 03/15] Add FreeBSD test workflow --- .github/workflows/freebsd.yml | 78 +++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 .github/workflows/freebsd.yml diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml new file mode 100644 index 00000000000..593bbceb8f9 --- /dev/null +++ b/.github/workflows/freebsd.yml @@ -0,0 +1,78 @@ +name: FreeBSD CI + +# Triggers the workflow on push or pull request or on demand +on: + workflow_dispatch: + push: + pull_request: + branches: [ develop ] + paths-ignore: + - '.github/CODEOWNERS' + - '.github/FUNDING.yml' + - 'doc/**' + - 'release_docs/**' + - 'ACKNOWLEDGEMENTS' + - 'LICENSE**' + - '**.md' + +# Using concurrency to cancel any in-progress job or run +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + freebsd-build-and-test: + runs-on: ubuntu-latest + name: FreeBSD 14.2 Build and Test + + # Don't run the action if the commit message says to skip CI + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Build and test on FreeBSD + uses: vmactions/freebsd-vm@v1 + with: + release: '14.2' + usesh: true + prepare: | + pkg install -y cmake ninja pkgconf bash curl + run: | + set -e + + # Configure the build + mkdir build + cd build + cmake -C ../config/cmake/cacheinit.cmake \ + -G Ninja \ + --log-level=VERBOSE \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + -DHDF5_ENABLE_ALL_WARNINGS:BOOL=ON \ + -DHDF5_ENABLE_PARALLEL:BOOL=OFF \ + -DHDF5_BUILD_CPP_LIB:BOOL=ON \ + -DHDF5_BUILD_FORTRAN:BOOL=OFF \ + -DHDF5_BUILD_JAVA:BOOL=OFF \ + -DHDF5_BUILD_DOC:BOOL=OFF \ + -DHDF5_ENABLE_ZLIB_SUPPORT:BOOL=ON \ + -DHDF5_ENABLE_SZIP_SUPPORT:BOOL=ON \ + -DLIBAEC_USE_LOCALCONTENT:BOOL=OFF \ + -DZLIB_USE_LOCALCONTENT:BOOL=OFF \ + -DHDF5_TEST_API:BOOL=ON \ + -DHDF5_TEST_SHELL_SCRIPTS:BOOL=OFF \ + -DENABLE_EXTENDED_TESTS:BOOL=OFF \ + .. + echo "" + + # Build + cmake --build . --parallel 3 --config Release + echo "" + + # Run tests + ctest . --parallel 2 -C Release -V + echo "" From 817dbb8eb0ef5d08e7298c7f3675c661db0b8a8f Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Wed, 22 Oct 2025 10:17:17 -0500 Subject: [PATCH 04/15] Test different FreeBSD versions --- .github/workflows/freebsd.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 593bbceb8f9..a39cf155772 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -26,11 +26,16 @@ permissions: jobs: freebsd-build-and-test: runs-on: ubuntu-latest - name: FreeBSD 14.2 Build and Test + name: FreeBSD ${{ matrix.freebsd-version }} Build and Test # Don't run the action if the commit message says to skip CI if: "!contains(github.event.head_commit.message, 'skip-ci')" + strategy: + fail-fast: false + matrix: + freebsd-version: ['13.5', '14.3', '15.0'] + steps: - name: Checkout repository uses: actions/checkout@v5 @@ -38,7 +43,7 @@ jobs: - name: Build and test on FreeBSD uses: vmactions/freebsd-vm@v1 with: - release: '14.2' + release: ${{ matrix.freebsd-version }} usesh: true prepare: | pkg install -y cmake ninja pkgconf bash curl From 38da26d9fde780a0a3b6f914cc3861584be625e3 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Wed, 22 Oct 2025 10:17:29 -0500 Subject: [PATCH 05/15] Add workflow for qsort_r fallback --- .github/workflows/test-qsort-fallback.yml | 87 +++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 .github/workflows/test-qsort-fallback.yml diff --git a/.github/workflows/test-qsort-fallback.yml b/.github/workflows/test-qsort-fallback.yml new file mode 100644 index 00000000000..a868a9c0384 --- /dev/null +++ b/.github/workflows/test-qsort-fallback.yml @@ -0,0 +1,87 @@ +name: Test qsort_r Fallback + +# Triggers the workflow on push or pull request or on demand +on: + workflow_dispatch: + push: + pull_request: + branches: [ develop ] + paths-ignore: + - '.github/CODEOWNERS' + - '.github/FUNDING.yml' + - 'doc/**' + - 'release_docs/**' + - 'ACKNOWLEDGEMENTS' + - 'LICENSE**' + - '**.md' + +# Using concurrency to cancel any in-progress job or run +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test-qsort-fallback: + runs-on: ${{ matrix.os }} + name: Test qsort_r fallback on ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + # Don't run the action if the commit message says to skip CI + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Set up build dependencies (Ubuntu) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y cmake gcc g++ zlib1g-dev + + - name: Set up build dependencies (macOS) + if: runner.os == 'macOS' + run: | + brew install cmake + + - name: Set up build dependencies (Windows) + if: runner.os == 'Windows' + uses: microsoft/setup-msbuild@v2 + + - name: Configure and build HDF5 + run: | + mkdir build + cd build + cmake -C ../config/cmake/cacheinit.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + -DHDF5_ENABLE_PARALLEL:BOOL=OFF \ + -DHDF5_BUILD_CPP_LIB:BOOL=OFF \ + -DHDF5_BUILD_FORTRAN:BOOL=OFF \ + -DHDF5_BUILD_JAVA:BOOL=OFF \ + -DHDF5_BUILD_DOC:BOOL=OFF \ + -DHDF5_ENABLE_ZLIB_SUPPORT:BOOL=ON \ + -DHDF5_ENABLE_SZIP_SUPPORT:BOOL=OFF \ + -DHDF5_TEST_API:BOOL=ON \ + -DHDF5_TEST_SHELL_SCRIPTS:BOOL=OFF \ + .. + cmake --build . --config Release --parallel 4 + shell: bash + + - name: Force fallback by disabling H5_HAVE_QSORT_REENTRANT + run: | + sed -i.bak 's/#define H5_HAVE_QSORT_REENTRANT 1/#define H5_HAVE_QSORT_REENTRANT 0/' build/src/H5pubconf.h + shell: bash + + - name: Run tests with fallback implementation + run: | + cd build + ctest --output-on-failure --parallel 4 -C Release + shell: bash From a4ac6a5eacfc34afe9289f9785389455cd845a40 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Wed, 22 Oct 2025 11:02:30 -0500 Subject: [PATCH 06/15] Fix FreeBSD <14.0 rtree usage --- src/H5private.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/H5private.h b/src/H5private.h index e89a45cf49e..c300cb2551d 100644 --- a/src/H5private.h +++ b/src/H5private.h @@ -776,7 +776,8 @@ H5_DLL void HDqsort_fallback(void *base, size_t nel, size_t size, #endif #ifndef HDqsort_r #ifdef H5_HAVE_QSORT_REENTRANT -#ifdef H5_HAVE_DARWIN +#if defined(H5_HAVE_DARWIN) || (defined(__FreeBSD__) && __FreeBSD__ < 14) +/* Darwin and FreeBSD < 14 use BSD-style qsort_r with different signature/argument order */ #define HDqsort_r(B, N, S, C, A) HDqsort_context(B, N, S, C, A) #else #define HDqsort_r(B, N, S, C, A) qsort_r(B, N, S, C, A) From c7ffe3a146a62d74c5f7d71520786ff9b815a694 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Wed, 22 Oct 2025 15:36:21 -0500 Subject: [PATCH 07/15] Correct fallback test workflow --- .github/workflows/test-qsort-fallback.yml | 18 +++++++++++++----- config/ConfigureChecks.cmake | 2 -- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test-qsort-fallback.yml b/.github/workflows/test-qsort-fallback.yml index a868a9c0384..091031f6151 100644 --- a/.github/workflows/test-qsort-fallback.yml +++ b/.github/workflows/test-qsort-fallback.yml @@ -40,6 +40,19 @@ jobs: - name: Checkout repository uses: actions/checkout@v5 + - name: Force qsort fallback by modifying ConfigureChecks.cmake + run: | + sed -i.bak '/CHECK_FUNCTION_EXISTS (qsort_r _HAVE_QSORT_R_TMP)/,/^endif ()$/{ + /CHECK_FUNCTION_EXISTS (qsort_r _HAVE_QSORT_R_TMP)/c\ + # Force qsort fallback for testing\ + set(${HDF_PREFIX}_HAVE_QSORT_REENTRANT 0) + /CHECK_FUNCTION_EXISTS (qsort_s _HAVE_QSORT_S_TMP)/d + /if (_HAVE_QSORT_R_TMP OR _HAVE_QSORT_S_TMP)/d + /set (${HDF_PREFIX}_HAVE_QSORT_REENTRANT 1)/d + /^endif ()$/d + }' config/ConfigureChecks.cmake + shell: bash + - name: Set up build dependencies (Ubuntu) if: runner.os == 'Linux' run: | @@ -75,11 +88,6 @@ jobs: cmake --build . --config Release --parallel 4 shell: bash - - name: Force fallback by disabling H5_HAVE_QSORT_REENTRANT - run: | - sed -i.bak 's/#define H5_HAVE_QSORT_REENTRANT 1/#define H5_HAVE_QSORT_REENTRANT 0/' build/src/H5pubconf.h - shell: bash - - name: Run tests with fallback implementation run: | cd build diff --git a/config/ConfigureChecks.cmake b/config/ConfigureChecks.cmake index 04918411e30..ae2948ed037 100644 --- a/config/ConfigureChecks.cmake +++ b/config/ConfigureChecks.cmake @@ -444,8 +444,6 @@ CHECK_FUNCTION_EXISTS (vasprintf ${HDF_PREFIX}_HAVE_VASPRINTF) CHECK_FUNCTION_EXISTS (waitpid ${HDF_PREFIX}_HAVE_WAITPID) # Check for reentrant qsort variants (qsort_r on Unix/BSD, qsort_s on Windows) -# Note: qsort_r has different signatures on GNU/Linux vs BSD/macOS, but we just -# check for existence here. Signature differences will be handled in the code. CHECK_FUNCTION_EXISTS (qsort_r _HAVE_QSORT_R_TMP) CHECK_FUNCTION_EXISTS (qsort_s _HAVE_QSORT_S_TMP) if (_HAVE_QSORT_R_TMP OR _HAVE_QSORT_S_TMP) From 337bdcf67f2c9d292a9f06618ab9e07cbe4666c6 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Wed, 22 Oct 2025 16:00:35 -0500 Subject: [PATCH 08/15] Drop FreeBSD 13.5 testing --- .github/workflows/freebsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index a39cf155772..afe5072aa7a 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - freebsd-version: ['13.5', '14.3', '15.0'] + freebsd-version: ['14.3', '15.0'] steps: - name: Checkout repository From c56ca00e91562555465ea440a3b6c697a32fec01 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Mon, 27 Oct 2025 09:48:06 -0500 Subject: [PATCH 09/15] Add error checking to TS functions --- src/H5system.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/H5system.c b/src/H5system.c index e78ec360e34..bbc61113dda 100644 --- a/src/H5system.c +++ b/src/H5system.c @@ -1475,15 +1475,27 @@ typedef struct HDqsort_fallback_context_t { } HDqsort_fallback_context_t; #ifdef H5_HAVE_THREADSAFE -/* Thread-local storage approach for thread-safe builds */ +/* Thread-local storage approach for thread-safe builds + * + * Note: The functions below use assertions instead of the HDF5 error stack because + * qsort and its variants return void, preventing propagation of errors. + */ static H5TS_key_t HDqsort_fallback_key; static H5TS_once_t HDqsort_fallback_key_once = H5TS_ONCE_INITIALIZER; static void HDqsort_fallback_key_init(void) { + herr_t ret = SUCCEED; + /* Create the thread-local storage key (no destructor needed) */ - H5TS_key_create(&HDqsort_fallback_key, NULL); + ret = H5TS_key_create(&HDqsort_fallback_key, NULL); + + /* Assert that initialization succeeded - cannot propagate errors from here */ + if (H5_UNLIKELY(ret < 0)) { + assert(false && "Failed to create TLS key for qsort fallback"); + (void)0; /* Ensure non-empty body even when asserts are disabled */ + } } static int @@ -1505,20 +1517,33 @@ HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void * void *arg) { HDqsort_fallback_context_t ctx; + herr_t ret; /* Ensure the TLS key is initialized */ - H5TS_once(&HDqsort_fallback_key_once, HDqsort_fallback_key_init); + ret = H5TS_once(&HDqsort_fallback_key_once, HDqsort_fallback_key_init); + if (H5_UNLIKELY(ret < 0)) { + assert(false && "Failed to initialize TLS key for qsort fallback"); + (void)0; /* Ensure non-empty body even when asserts are disabled */ + } ctx.gnu_compar = compar; ctx.gnu_arg = arg; /* Store context in thread-local storage */ - H5TS_key_set_value(HDqsort_fallback_key, &ctx); + ret = H5TS_key_set_value(HDqsort_fallback_key, &ctx); + if (H5_UNLIKELY(ret < 0)) { + assert(false && "Failed to set TLS value for qsort fallback"); + (void)0; /* Ensure non-empty body even when asserts are disabled */ + } qsort(base, nel, size, HDqsort_fallback_wrapper); /* Clear the thread-local storage */ - H5TS_key_set_value(HDqsort_fallback_key, NULL); + ret = H5TS_key_set_value(HDqsort_fallback_key, NULL); + if (H5_UNLIKELY(ret < 0)) { + assert(false && "Failed to clear TLS value for qsort fallback"); + (void)0; /* Ensure non-empty body even when asserts are disabled */ + } } #else From 3895bca78b933603f20b89b9af18d8169571e547 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Mon, 27 Oct 2025 11:43:45 -0500 Subject: [PATCH 10/15] Re-add FreeBSD 13.5 --- .github/workflows/freebsd.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index afe5072aa7a..a39cf155772 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - freebsd-version: ['14.3', '15.0'] + freebsd-version: ['13.5', '14.3', '15.0'] steps: - name: Checkout repository From 6b00fcfad885458e9020c838ae6f20a5a1f3bd38 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Mon, 27 Oct 2025 12:10:35 -0500 Subject: [PATCH 11/15] Return error code from HDqsort_r --- src/H5RT.c | 4 +++- src/H5private.h | 11 ++++++----- src/H5system.c | 47 +++++++++++++++++++++++++---------------------- 3 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/H5RT.c b/src/H5RT.c index 606fd08fc69..7a19fa819c9 100644 --- a/src/H5RT.c +++ b/src/H5RT.c @@ -394,7 +394,9 @@ H5RT__bulk_load(H5RT_node_t *node, int rank, H5RT_leaf_t *leaves, size_t count, if (prev_sort_dim != rank - 1) { assert(prev_sort_dim < rank - 1); sort_dim = prev_sort_dim + 1; - HDqsort_r((void *)leaves, count, sizeof(H5RT_leaf_t), H5RT__leaf_compare, (void *)&sort_dim); + if (H5_UNLIKELY(HDqsort_r((void *)leaves, count, sizeof(H5RT_leaf_t), H5RT__leaf_compare, + (void *)&sort_dim) < 0)) + HGOTO_ERROR(H5E_INTERNAL, H5E_CANTSORT, FAIL, "failed to sort R-tree leaves"); } else { sort_dim = prev_sort_dim; diff --git a/src/H5private.h b/src/H5private.h index c300cb2551d..7c0c1dbfa8f 100644 --- a/src/H5private.h +++ b/src/H5private.h @@ -650,13 +650,13 @@ H5_DLL H5_ATTR_CONST int Nflock(int fd, int operation); #endif /* HDflock */ #if defined(H5_HAVE_WIN32_API) || defined(H5_HAVE_DARWIN) || (defined(__FreeBSD__) && __FreeBSD__ < 14) -H5_DLL void HDqsort_context(void *base, size_t nel, size_t size, - int (*compar)(const void *, const void *, void *), void *arg); +H5_DLL herr_t HDqsort_context(void *base, size_t nel, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); #endif #ifndef H5_HAVE_QSORT_REENTRANT -H5_DLL void HDqsort_fallback(void *base, size_t nel, size_t size, - int (*compar)(const void *, const void *, void *), void *arg); +H5_DLL herr_t HDqsort_fallback(void *base, size_t nel, size_t size, + int (*compar)(const void *, const void *, void *), void *arg); #endif #ifndef HDfseek @@ -780,7 +780,8 @@ H5_DLL void HDqsort_fallback(void *base, size_t nel, size_t size, /* Darwin and FreeBSD < 14 use BSD-style qsort_r with different signature/argument order */ #define HDqsort_r(B, N, S, C, A) HDqsort_context(B, N, S, C, A) #else -#define HDqsort_r(B, N, S, C, A) qsort_r(B, N, S, C, A) +/* Wrap native GNU qsort_r to vacuously return success */ +#define HDqsort_r(B, N, S, C, A) (qsort_r(B, N, S, C, A), SUCCEED) #endif #else /* No native qsort_r/qsort_s available - use fallback implementation */ diff --git a/src/H5system.c b/src/H5system.c index bbc61113dda..fb96ca15b2c 100644 --- a/src/H5system.c +++ b/src/H5system.c @@ -1441,7 +1441,7 @@ HDqsort_context_wrapper_func(void *wrapper_arg, const void *a, const void *b) return w->gnu_compar(a, b, w->gnu_arg); } -void +herr_t HDqsort_context(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), void *arg) { @@ -1454,6 +1454,7 @@ HDqsort_context(void *base, size_t nel, size_t size, int (*compar)(const void *, /* Old BSD-style: context parameter comes before comparator function */ qsort_r(base, nel, size, &wrapper, HDqsort_context_wrapper_func); #endif + return SUCCEED; } #endif @@ -1477,8 +1478,10 @@ typedef struct HDqsort_fallback_context_t { #ifdef H5_HAVE_THREADSAFE /* Thread-local storage approach for thread-safe builds * - * Note: The functions below use assertions instead of the HDF5 error stack because - * qsort and its variants return void, preventing propagation of errors. + * Error Handling Note: + * HDqsort_fallback_key_init() still uses assertions because it's a callback with void signature. + * However, HDqsort_fallback() now returns herr_t and can propagate TLS operation failures + * through the normal HDF5 error stack. */ static H5TS_key_t HDqsort_fallback_key; static H5TS_once_t HDqsort_fallback_key_once = H5TS_ONCE_INITIALIZER; @@ -1489,7 +1492,9 @@ HDqsort_fallback_key_init(void) herr_t ret = SUCCEED; /* Create the thread-local storage key (no destructor needed) */ - ret = H5TS_key_create(&HDqsort_fallback_key, NULL); + /* If this operation fails, it will be detected shortly during + * HDqsort_fallback when operations are attempted on the non-existent key */ + H5TS_key_create(&HDqsort_fallback_key, NULL); /* Assert that initialization succeeded - cannot propagate errors from here */ if (H5_UNLIKELY(ret < 0)) { @@ -1512,38 +1517,34 @@ HDqsort_fallback_wrapper(const void *a, const void *b) return ctx->gnu_compar(a, b, ctx->gnu_arg); } -void +herr_t HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), void *arg) { HDqsort_fallback_context_t ctx; - herr_t ret; + herr_t ret_value = SUCCEED; + + FUNC_ENTER_NOAPI_NOERR /* Ensure the TLS key is initialized */ - ret = H5TS_once(&HDqsort_fallback_key_once, HDqsort_fallback_key_init); - if (H5_UNLIKELY(ret < 0)) { - assert(false && "Failed to initialize TLS key for qsort fallback"); - (void)0; /* Ensure non-empty body even when asserts are disabled */ - } + if (H5_UNLIKELY(H5TS_once(&HDqsort_fallback_key_once, HDqsort_fallback_key_init) < 0)) + HGOTO_ERROR(H5E_INTERNAL, H5E_CANTINIT, FAIL, "failed to initialize TLS key for qsort"); ctx.gnu_compar = compar; ctx.gnu_arg = arg; /* Store context in thread-local storage */ - ret = H5TS_key_set_value(HDqsort_fallback_key, &ctx); - if (H5_UNLIKELY(ret < 0)) { - assert(false && "Failed to set TLS value for qsort fallback"); - (void)0; /* Ensure non-empty body even when asserts are disabled */ - } + if (H5_UNLIKELY(H5TS_key_set_value(HDqsort_fallback_key, &ctx) < 0)) + HGOTO_ERROR(H5E_INTERNAL, H5E_CANTSET, FAIL, "failed to set TLS context for qsort"); qsort(base, nel, size, HDqsort_fallback_wrapper); /* Clear the thread-local storage */ - ret = H5TS_key_set_value(HDqsort_fallback_key, NULL); - if (H5_UNLIKELY(ret < 0)) { - assert(false && "Failed to clear TLS value for qsort fallback"); - (void)0; /* Ensure non-empty body even when asserts are disabled */ - } + if (H5_UNLIKELY(H5TS_key_set_value(HDqsort_fallback_key, NULL) < 0)) + HGOTO_ERROR(H5E_INTERNAL, H5E_CANTSET, FAIL, "failed to clear TLS context for qsort"); + +done: + FUNC_LEAVE_NOAPI(ret_value) } #else @@ -1557,7 +1558,7 @@ HDqsort_fallback_wrapper(const void *a, const void *b) return HDqsort_fallback_global_ctx->gnu_compar(a, b, HDqsort_fallback_global_ctx->gnu_arg); } -void +herr_t HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), void *arg) { @@ -1573,6 +1574,8 @@ HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void * /* Clear the global pointer */ HDqsort_fallback_global_ctx = NULL; + + return SUCCEED; } #endif /* H5_HAVE_THREADSAFE */ From 017ae727a08c0019c4e394e3222101c8c1b53e89 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Mon, 27 Oct 2025 12:19:35 -0500 Subject: [PATCH 12/15] Add NetBSD 10 workflow --- .github/workflows/netbsd.yml | 99 +++++++++++++++++++++++ .github/workflows/test-qsort-fallback.yml | 95 ---------------------- 2 files changed, 99 insertions(+), 95 deletions(-) create mode 100644 .github/workflows/netbsd.yml delete mode 100644 .github/workflows/test-qsort-fallback.yml diff --git a/.github/workflows/netbsd.yml b/.github/workflows/netbsd.yml new file mode 100644 index 00000000000..cc80f1c7246 --- /dev/null +++ b/.github/workflows/netbsd.yml @@ -0,0 +1,99 @@ +name: NetBSD CI + +# Triggers the workflow on push or pull request or on demand +on: + workflow_dispatch: + push: + pull_request: + branches: [ develop ] + paths-ignore: + - '.github/CODEOWNERS' + - '.github/FUNDING.yml' + - 'doc/**' + - 'release_docs/**' + - 'ACKNOWLEDGEMENTS' + - 'LICENSE**' + - '**.md' + +# Using concurrency to cancel any in-progress job or run +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + netbsd-build-and-test: + runs-on: ubuntu-latest + name: NetBSD ${{ matrix.netbsd-version }} Build and Test + + # Don't run the action if the commit message says to skip CI + if: "!contains(github.event.head_commit.message, 'skip-ci')" + + strategy: + fail-fast: false + matrix: + netbsd-version: ['10.1'] + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Build and test on NetBSD + uses: vmactions/netbsd-vm@v1 + with: + release: ${{ matrix.netbsd-version }} + usesh: true + prepare: | + /usr/sbin/pkg_add -v pkgin + pkgin -y update + pkgin -y install cmake gmake pkgconf curl gcc13 + echo "Package installation completed with exit code: $?" + run: | + set -e + + # Set up library path for gcc13 + export LD_LIBRARY_PATH=/usr/pkg/gcc13/lib:$LD_LIBRARY_PATH + + # Get number of processors (NetBSD uses sysctl in /sbin) + NPROC=$(/sbin/sysctl -n hw.ncpu) + echo "Number of processors: $NPROC" + + # Configure the build + mkdir build + cd build + cmake -C ../config/cmake/cacheinit.cmake \ + --log-level=VERBOSE \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER=/usr/pkg/gcc13/bin/gcc \ + -DCMAKE_CXX_COMPILER=/usr/pkg/gcc13/bin/g++ \ + -DCMAKE_MAKE_PROGRAM=/usr/pkg/bin/gmake \ + -DBUILD_SHARED_LIBS:BOOL=ON \ + -DHDF5_ENABLE_ALL_WARNINGS:BOOL=ON \ + -DHDF5_ENABLE_PARALLEL:BOOL=OFF \ + -DHDF5_BUILD_CPP_LIB:BOOL=OFF \ + -DHDF5_BUILD_FORTRAN:BOOL=OFF \ + -DHDF5_BUILD_JAVA:BOOL=OFF \ + -DHDF5_BUILD_DOC:BOOL=OFF \ + -DHDF5_BUILD_HL_LIB:BOOL=OFF \ + -DHDF5_ENABLE_ZLIB_SUPPORT:BOOL=ON \ + -DHDF5_ENABLE_SZIP_SUPPORT:BOOL=ON \ + -DLIBAEC_USE_LOCALCONTENT:BOOL=OFF \ + -DZLIB_USE_LOCALCONTENT:BOOL=OFF \ + -DHDF5_TEST_API:BOOL=ON \ + -DHDF5_TEST_SHELL_SCRIPTS:BOOL=OFF \ + -DENABLE_EXTENDED_TESTS:BOOL=OFF \ + .. + echo "" + + # Build + cmake --build . --parallel $NPROC + echo "" + + # TODO: Later threadsafe tests (rwlock 2+) timeout - should be investigated + # Skipping ttsafe for now to test qsort_r fallback + + # Run tests (excluding ttsafe which times out) + ctest . --parallel $NPROC -V -E ttsafe + echo "" diff --git a/.github/workflows/test-qsort-fallback.yml b/.github/workflows/test-qsort-fallback.yml deleted file mode 100644 index 091031f6151..00000000000 --- a/.github/workflows/test-qsort-fallback.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: Test qsort_r Fallback - -# Triggers the workflow on push or pull request or on demand -on: - workflow_dispatch: - push: - pull_request: - branches: [ develop ] - paths-ignore: - - '.github/CODEOWNERS' - - '.github/FUNDING.yml' - - 'doc/**' - - 'release_docs/**' - - 'ACKNOWLEDGEMENTS' - - 'LICENSE**' - - '**.md' - -# Using concurrency to cancel any in-progress job or run -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - test-qsort-fallback: - runs-on: ${{ matrix.os }} - name: Test qsort_r fallback on ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - - # Don't run the action if the commit message says to skip CI - if: "!contains(github.event.head_commit.message, 'skip-ci')" - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Force qsort fallback by modifying ConfigureChecks.cmake - run: | - sed -i.bak '/CHECK_FUNCTION_EXISTS (qsort_r _HAVE_QSORT_R_TMP)/,/^endif ()$/{ - /CHECK_FUNCTION_EXISTS (qsort_r _HAVE_QSORT_R_TMP)/c\ - # Force qsort fallback for testing\ - set(${HDF_PREFIX}_HAVE_QSORT_REENTRANT 0) - /CHECK_FUNCTION_EXISTS (qsort_s _HAVE_QSORT_S_TMP)/d - /if (_HAVE_QSORT_R_TMP OR _HAVE_QSORT_S_TMP)/d - /set (${HDF_PREFIX}_HAVE_QSORT_REENTRANT 1)/d - /^endif ()$/d - }' config/ConfigureChecks.cmake - shell: bash - - - name: Set up build dependencies (Ubuntu) - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install -y cmake gcc g++ zlib1g-dev - - - name: Set up build dependencies (macOS) - if: runner.os == 'macOS' - run: | - brew install cmake - - - name: Set up build dependencies (Windows) - if: runner.os == 'Windows' - uses: microsoft/setup-msbuild@v2 - - - name: Configure and build HDF5 - run: | - mkdir build - cd build - cmake -C ../config/cmake/cacheinit.cmake \ - -DCMAKE_BUILD_TYPE=Release \ - -DBUILD_SHARED_LIBS:BOOL=ON \ - -DHDF5_ENABLE_PARALLEL:BOOL=OFF \ - -DHDF5_BUILD_CPP_LIB:BOOL=OFF \ - -DHDF5_BUILD_FORTRAN:BOOL=OFF \ - -DHDF5_BUILD_JAVA:BOOL=OFF \ - -DHDF5_BUILD_DOC:BOOL=OFF \ - -DHDF5_ENABLE_ZLIB_SUPPORT:BOOL=ON \ - -DHDF5_ENABLE_SZIP_SUPPORT:BOOL=OFF \ - -DHDF5_TEST_API:BOOL=ON \ - -DHDF5_TEST_SHELL_SCRIPTS:BOOL=OFF \ - .. - cmake --build . --config Release --parallel 4 - shell: bash - - - name: Run tests with fallback implementation - run: | - cd build - ctest --output-on-failure --parallel 4 -C Release - shell: bash From 82447cfa160b833b06b70165eaba684687136f28 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Tue, 28 Oct 2025 10:29:46 -0500 Subject: [PATCH 13/15] Use OpenBSD 7.5 for qsort_r fallback verification instead --- .github/workflows/{netbsd.yml => openbsd.yml} | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) rename .github/workflows/{netbsd.yml => openbsd.yml} (66%) diff --git a/.github/workflows/netbsd.yml b/.github/workflows/openbsd.yml similarity index 66% rename from .github/workflows/netbsd.yml rename to .github/workflows/openbsd.yml index cc80f1c7246..b3ceb68c977 100644 --- a/.github/workflows/netbsd.yml +++ b/.github/workflows/openbsd.yml @@ -1,4 +1,4 @@ -name: NetBSD CI +name: OpenBSD CI # Triggers the workflow on push or pull request or on demand on: @@ -24,9 +24,9 @@ permissions: contents: read jobs: - netbsd-build-and-test: + openbsd-build-and-test: runs-on: ubuntu-latest - name: NetBSD ${{ matrix.netbsd-version }} Build and Test + name: OpenBSD ${{ matrix.openbsd-version }} Build and Test # Don't run the action if the commit message says to skip CI if: "!contains(github.event.head_commit.message, 'skip-ci')" @@ -34,29 +34,27 @@ jobs: strategy: fail-fast: false matrix: - netbsd-version: ['10.1'] + openbsd-version: ['7.5'] steps: - name: Checkout repository uses: actions/checkout@v5 - - name: Build and test on NetBSD - uses: vmactions/netbsd-vm@v1 + - name: Build and test on OpenBSD + uses: vmactions/openbsd-vm@v1 with: - release: ${{ matrix.netbsd-version }} + release: ${{ matrix.openbsd-version }} usesh: true prepare: | - /usr/sbin/pkg_add -v pkgin - pkgin -y update - pkgin -y install cmake gmake pkgconf curl gcc13 - echo "Package installation completed with exit code: $?" + echo "https://ftp.openbsd.org/pub/OpenBSD" > /etc/installurl + pkg_add cmake gmake pkgconf curl gcc-11.2.0p11 run: | set -e - # Set up library path for gcc13 - export LD_LIBRARY_PATH=/usr/pkg/gcc13/lib:$LD_LIBRARY_PATH + # Set up library path for gcc + export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - # Get number of processors (NetBSD uses sysctl in /sbin) + # Get number of processors (OpenBSD uses sysctl in /sbin) NPROC=$(/sbin/sysctl -n hw.ncpu) echo "Number of processors: $NPROC" @@ -66,9 +64,9 @@ jobs: cmake -C ../config/cmake/cacheinit.cmake \ --log-level=VERBOSE \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_COMPILER=/usr/pkg/gcc13/bin/gcc \ - -DCMAKE_CXX_COMPILER=/usr/pkg/gcc13/bin/g++ \ - -DCMAKE_MAKE_PROGRAM=/usr/pkg/bin/gmake \ + -DCMAKE_C_COMPILER=egcc \ + -DCMAKE_CXX_COMPILER=eg++ \ + -DCMAKE_MAKE_PROGRAM=gmake \ -DBUILD_SHARED_LIBS:BOOL=ON \ -DHDF5_ENABLE_ALL_WARNINGS:BOOL=ON \ -DHDF5_ENABLE_PARALLEL:BOOL=OFF \ @@ -91,9 +89,6 @@ jobs: cmake --build . --parallel $NPROC echo "" - # TODO: Later threadsafe tests (rwlock 2+) timeout - should be investigated - # Skipping ttsafe for now to test qsort_r fallback - - # Run tests (excluding ttsafe which times out) - ctest . --parallel $NPROC -V -E ttsafe + # Run tests + ctest . --parallel $NPROC echo "" From 4f6b8cb917ba8134d6aec46367a7cf3a95951a50 Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Tue, 28 Oct 2025 10:44:15 -0500 Subject: [PATCH 14/15] Remove assert --- src/H5system.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/H5system.c b/src/H5system.c index fb96ca15b2c..5d53810d944 100644 --- a/src/H5system.c +++ b/src/H5system.c @@ -1489,18 +1489,10 @@ static H5TS_once_t HDqsort_fallback_key_once = H5TS_ONCE_INITIALIZER; static void HDqsort_fallback_key_init(void) { - herr_t ret = SUCCEED; - /* Create the thread-local storage key (no destructor needed) */ /* If this operation fails, it will be detected shortly during * HDqsort_fallback when operations are attempted on the non-existent key */ H5TS_key_create(&HDqsort_fallback_key, NULL); - - /* Assert that initialization succeeded - cannot propagate errors from here */ - if (H5_UNLIKELY(ret < 0)) { - assert(false && "Failed to create TLS key for qsort fallback"); - (void)0; /* Ensure non-empty body even when asserts are disabled */ - } } static int From b0939288a38a6d63d8709795ca9d51247bea1c6e Mon Sep 17 00:00:00 2001 From: Matthew Larson Date: Tue, 28 Oct 2025 11:18:40 -0500 Subject: [PATCH 15/15] Remove threadsafe qsort_r implementation --- src/H5RT.c | 5 ++++ src/H5system.c | 74 ++------------------------------------------------ 2 files changed, 8 insertions(+), 71 deletions(-) diff --git a/src/H5RT.c b/src/H5RT.c index 7a19fa819c9..8a4dd62e856 100644 --- a/src/H5RT.c +++ b/src/H5RT.c @@ -461,6 +461,11 @@ H5RT__bulk_load(H5RT_node_t *node, int rank, H5RT_leaf_t *leaves, size_t count, * On success, the R-tree takes ownership of the caller-allocated * leaves array. * + * NOTE: This routine uses a global variable internally, and + * is therefore not thread-safe. See the 'qsort_r_threadsafe' + * branch of the HDF5 GitHub repository for a beta + * implementation that is threadsafe. + * * Return: A valid pointer to the new R-tree on success/NULL on failure * *------------------------------------------------------------------------- diff --git a/src/H5system.c b/src/H5system.c index 5d53810d944..4610c770aa2 100644 --- a/src/H5system.c +++ b/src/H5system.c @@ -1461,12 +1461,10 @@ HDqsort_context(void *base, size_t nel, size_t size, int (*compar)(const void *, /* * HDqsort_fallback - Fallback qsort implementation for platforms without qsort_r/qsort_s * - * For platforms that don't provide any reentrant qsort variant, this fallback uses - * thread-local storage (when thread-safety is - * enabled) or a global variable to store the comparator context, then uses standard qsort(). + * This implementation is not threadsafe, since it uses a global variable to store the + * comparator context, then uses standard qsort(). A beta branch of a threadsafe implementation + * of these routines may be found in the 'qsort_r_threadsafe' branch of the HDF5 GitHub repository. * - * The threadsafe version uses thread-local storage but does not support recursive sorting (a - * comparator calling HDqsort_r) as it would overwrite the previous context. */ #ifndef H5_HAVE_QSORT_REENTRANT @@ -1475,71 +1473,6 @@ typedef struct HDqsort_fallback_context_t { void *gnu_arg; } HDqsort_fallback_context_t; -#ifdef H5_HAVE_THREADSAFE -/* Thread-local storage approach for thread-safe builds - * - * Error Handling Note: - * HDqsort_fallback_key_init() still uses assertions because it's a callback with void signature. - * However, HDqsort_fallback() now returns herr_t and can propagate TLS operation failures - * through the normal HDF5 error stack. - */ -static H5TS_key_t HDqsort_fallback_key; -static H5TS_once_t HDqsort_fallback_key_once = H5TS_ONCE_INITIALIZER; - -static void -HDqsort_fallback_key_init(void) -{ - /* Create the thread-local storage key (no destructor needed) */ - /* If this operation fails, it will be detected shortly during - * HDqsort_fallback when operations are attempted on the non-existent key */ - H5TS_key_create(&HDqsort_fallback_key, NULL); -} - -static int -HDqsort_fallback_wrapper(const void *a, const void *b) -{ - HDqsort_fallback_context_t *ctx = NULL; - - /* Retrieve the context from thread-local storage - * This should never fail since we just set it in HDqsort_fallback() */ - if (H5_UNLIKELY(H5TS_key_get_value(HDqsort_fallback_key, (void **)&ctx) < 0)) - return 0; /* Should never happen, but return 0 (equal) if it does */ - - /* Call the original GNU-style comparator with context */ - return ctx->gnu_compar(a, b, ctx->gnu_arg); -} - -herr_t -HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void *, const void *, void *), - void *arg) -{ - HDqsort_fallback_context_t ctx; - herr_t ret_value = SUCCEED; - - FUNC_ENTER_NOAPI_NOERR - - /* Ensure the TLS key is initialized */ - if (H5_UNLIKELY(H5TS_once(&HDqsort_fallback_key_once, HDqsort_fallback_key_init) < 0)) - HGOTO_ERROR(H5E_INTERNAL, H5E_CANTINIT, FAIL, "failed to initialize TLS key for qsort"); - - ctx.gnu_compar = compar; - ctx.gnu_arg = arg; - - /* Store context in thread-local storage */ - if (H5_UNLIKELY(H5TS_key_set_value(HDqsort_fallback_key, &ctx) < 0)) - HGOTO_ERROR(H5E_INTERNAL, H5E_CANTSET, FAIL, "failed to set TLS context for qsort"); - - qsort(base, nel, size, HDqsort_fallback_wrapper); - - /* Clear the thread-local storage */ - if (H5_UNLIKELY(H5TS_key_set_value(HDqsort_fallback_key, NULL) < 0)) - HGOTO_ERROR(H5E_INTERNAL, H5E_CANTSET, FAIL, "failed to clear TLS context for qsort"); - -done: - FUNC_LEAVE_NOAPI(ret_value) -} - -#else /* Non-threadsafe: use global variable */ static HDqsort_fallback_context_t *HDqsort_fallback_global_ctx = NULL; @@ -1569,6 +1502,5 @@ HDqsort_fallback(void *base, size_t nel, size_t size, int (*compar)(const void * return SUCCEED; } -#endif /* H5_HAVE_THREADSAFE */ #endif /* !H5_HAVE_QSORT_REENTRANT */