Implement fallback for systems without qsort_r (#5931)

Also includes a new workflow to test FreeBSD, which has a different qsort signature and was previously untested
Resolves #5896
This commit is contained in:
Matt L
2025-11-02 22:06:34 -06:00
committed by GitHub
parent 85f7f8f083
commit 10c2ddc3cb
7 changed files with 260 additions and 6 deletions
+83
View File
@@ -0,0 +1,83 @@
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 ${{ 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
- name: Build and test on FreeBSD
uses: vmactions/freebsd-vm@v1
with:
release: ${{ matrix.freebsd-version }}
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 ""
+94
View File
@@ -0,0 +1,94 @@
name: OpenBSD 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:
openbsd-build-and-test:
runs-on: ubuntu-latest
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')"
strategy:
fail-fast: false
matrix:
openbsd-version: ['7.5']
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Build and test on OpenBSD
uses: vmactions/openbsd-vm@v1
with:
release: ${{ matrix.openbsd-version }}
usesh: true
prepare: |
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 gcc
export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
# Get number of processors (OpenBSD 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=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 \
-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 ""
# Run tests
ctest . --parallel $NPROC
echo ""
+7
View File
@@ -443,6 +443,13 @@ 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)
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
#-----------------------------------------------------------------------------
+8 -1
View File
@@ -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;
@@ -459,6 +461,11 @@ done:
* 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
*
*-------------------------------------------------------------------------
+16 -4
View File
@@ -650,8 +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 herr_t HDqsort_fallback(void *base, size_t nel, size_t size,
int (*compar)(const void *, const void *, void *), void *arg);
#endif
#ifndef HDfseek
@@ -770,10 +775,17 @@ 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_DARWIN
#ifdef H5_HAVE_QSORT_REENTRANT
#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)
/* 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 */
#define HDqsort_r(B, N, S, C, A) HDqsort_fallback(B, N, S, C, A)
#endif
#endif
#ifndef HDvasprintf
+3
View File
@@ -258,6 +258,9 @@
/* Define to 1 if you have the <pwd.h> 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@
+49 -1
View File
@@ -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,5 +1454,53 @@ 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
/*
* HDqsort_fallback - Fallback qsort implementation for platforms without qsort_r/qsort_s
*
* 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.
*
*/
#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;
/* 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);
}
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;
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;
return SUCCEED;
}
#endif /* !H5_HAVE_QSORT_REENTRANT */