Skip to content

Load bundled HDF core libraries before the JNI wrappers initialize - #480

Open
mattjala wants to merge 4 commits into
HDFGroup:masterfrom
mattjala:native_lib_preload
Open

Load bundled HDF core libraries before the JNI wrappers initialize#480
mattjala wants to merge 4 commits into
HDFGroup:masterfrom
mattjala:native_lib_preload

Conversation

@mattjala

@mattjala mattjala commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

HDFView sometimes fails to start when another HDF5 or HDF4 installation is visible to the operating system's dynamic loader. The workaround so far has been to require users to hand-edit their PATH to avoid detection of the pre-existing libraries.

The cause is that two native libraries load by two different mechanisms. The JNI wrapper (hdf5_java) is loaded by the JVM and honors java.library.path, so our packaging already pins it correctly. But the wrapper lists the core library (libhdf5.so.320 on Linux, hdf5.dll on Windows) as a NEEDED dependency, and that one is resolved by the OS loader rather than the JVM. On Linux the loader checks LD_LIBRARY_PATH before the RUNPATH we ship, and on Windows the search order includes PATH. Either way a foreign copy can be detected first.

This change loads the bundled core libraries by absolute path before anything touches the wrapper. Once a library is mapped into the process, the loader satisfies the wrapper's dependency from the already-mapped copy, matching on soname on Linux and on module base name on Windows, and never searches PATH or LD_LIBRARY_PATH at all.

NativeLibraryLoader looks for each library in -Dhdfview.nativedir, then -Dhdfview.root (jpackage already sets this to $APPDIR), then the directory named by -Dhdf.hdf5lib.H5.hdf5lib, then each entry of java.library.path. The call sits at the top of FileFormat's static initializer, which is the earliest point the wrapper can initialize.

Resolves #459

@mattjala
mattjala requested a review from lrknox as a code owner August 11, 2026 21:22
@mattjala mattjala added the Component - Build Build system improvement label Aug 11, 2026
@mattjala mattjala moved this from To be triaged to Scheduled/On-Deck in HDFView - TRIAGE & TRACK Aug 11, 2026
Comment thread object/src/main/java/hdf/object/NativeLibraryLoader.java Fixed
@mattjala
mattjala force-pushed the native_lib_preload branch from 3a3312c to 04dc9a8 Compare August 11, 2026 21:27
Comment thread object/src/main/java/hdf/object/FileFormat.java Outdated
@jhendersonHDF

jhendersonHDF commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator

Before getting into some fairly crazy platform-specific workarounds here (to be honest, package_files/linux/pin-rpath.py is absurd to a degree that I wouldn't be comfortable maintaining), let's start with Windows alone and try to fix the issue directly at the packaging step. The powershell script to relocate the DLLs is reasonable enough that I'd be happy with it, but really it seems like something that could be solved better at the point where the "application image" is setup. Based on the discussion in https://stackoverflow.com/questions/78030186/jpackage-openjdk-cant-deploy-app-with-jni-dependencies, it seems that we should generally have control over where the DLLs are going to be placed and there are several ways we can accomplish having them directly next to the launcher executable without needing a post-processing "relocate" step. There are also likely much better ways we can approach the packaging step for Windows, such as the JMOD approach mentioned.

At least on Linux and MacOS, there shouldn't be an issue with allowing the JVM to try loading a shared HDF5 library outside the bundled ones in the first place, as .so/.dylib versioning should take care of the details and prevent loading an HDF5 library that is incompatible. In theory, we should be able to do the same thing on Windows as well, but I'm not familiar with the details of DLL versioning there.

I verified the above by forcing HDFView to load HDF5 2.2.0 (libhdf5.so.320.2.0) while it was built against 2.0.0 (libhdf5.so.320.0.0):

ldd libhdf5_java.so
ldd: warning: you do not have execution permission for `./libhdf5_java.so'
	linux-vdso.so.1 (0x00007f0daa8bf000)
	libhdf5.so.320 => ~/Downloads/HDFView/lib/app/libhdf5.so.320 (0x00007f0daa200000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f0da9e00000)
	libm.so.6 => /lib64/libm.so.6 (0x00007f0daa104000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0daa8c1000)

LD_LIBRARY_PATH=/opt/hdf5_2_2_0/lib64/ ./HDFView
lsof -p 23199
...
HDFView 23199 jhenderson mem       REG                8,4   6021896   9961533 /opt/hdf5_2_2_0/lib64/libhdf5.so.320.2.0

HDFView sometimes fails to start when another HDF5 or HDF4 installation is
visible to the operating system's dynamic loader. The workaround so far has been
to require users to hand-edit their PATH to avoid detection of the
pre-existing libraries.

The cause is that two native libraries load by two different mechanisms. The JNI
wrapper (hdf5_java) is loaded by the JVM and honors java.library.path, so our
packaging already pins it correctly. But the wrapper lists the core library
(libhdf5.so.320 on Linux, hdf5.dll on Windows) as a NEEDED dependency, and that
one is resolved by the OS loader rather than the JVM. On Linux the loader checks
LD_LIBRARY_PATH before the RUNPATH we ship, and on Windows the search order
includes PATH. Either way a foreign copy can be detected first.

This change loads the bundled core libraries by absolute path before anything
touches the wrapper. Once a library is mapped into the process, the loader
satisfies the wrapper's dependency from the already-mapped copy, matching on
soname on Linux and on module base name on Windows, and never searches PATH or
LD_LIBRARY_PATH at all.

NativeLibraryLoader looks for each library in -Dhdfview.nativedir, then
-Dhdfview.root (jpackage already sets this to $APPDIR), then the directory named
by -Dhdf.hdf5lib.H5.hdf5lib, then each entry of java.library.path. The call sits
at the top of FileFormat's static initializer, which is the earliest point the
wrapper can initialize.
The loading logic is now addressed per platform where the bundle is assembled,
and HDFView itself carries no library-loading logic.

Linux: The new pin-rpath.py script refiles the HDF binaries' existing search
path under DT_RPATH instead of DT_RUNPATH, so its consulted before
LD_LIBRARY_PATH.

Windows: The core DLLs move beside HDFView.exe, whose directory is searched
ahead of both the system folder and PATH.

macOS: No specific extra work needed, and the bundled libraries are already
found first.

PATH and LD_LIBRARY_PATH should no longer redirect a packaged HDFView to a
different HDF build.
@mattjala
mattjala force-pushed the native_lib_preload branch from 88b1fd9 to 4120171 Compare August 21, 2026 14:40
@mattjala mattjala added this to the HDFView 3.x.x milestone Aug 21, 2026
@nbagha1 nbagha1 moved this from On-Deck to In progress in HDFView - TRIAGE & TRACK Aug 21, 2026
Comment thread hdfview/pom.xml
<!-- target only separates them out when this profile is also passing them -->
<!-- to jpackage. -->
<jpackage.corelib.include>*.dll</jpackage.corelib.include>
<jpackage.corelib.exclude>*_java.dll</jpackage.corelib.exclude>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's anything particularly wrong with this approach, but it would probably be simpler for this workflow's logic to just put everything next to the .exe and adjust java.library.path

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Component - Build Build system improvement

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

Windows: HDF4/5 on PATH override HDF4/5 packaged with installer/binary

4 participants