Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidy up and share (between processes) temporary copies of JNI shared library loaded from JAR #13318

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

alanpaxton
Copy link
Contributor

@alanpaxton alanpaxton commented Jan 20, 2025

This fixes #13185

NativeLibraryLoader.java (part of RocksJava) goes through a number of stages to find an instance of the rocksdb native library to run against. As a backstop, it extracts a copy of the library from the rocksdb jni jar (the build bundles the library into the jar). The library is stored in the temporary directory, and the Java process is requested to deleteOnExit() the temporary file. This deletion doesn't work when the Java process is killed (-9) and as a result, many copies of the (large) library file can build up.

This PR adds a mechanism to find/create a temporary library in a known subdirectory of the temporary directory; the name is based on the name requested by the client, plus some digestification to distinguish between files with version names. The Java file locking mechanism is used to lock a blessed lock file in the subdirectory, and a separate 0-length lock file is created for each process which is using the library. The last process to stop using the library deletes the temporary library, but NOT the directory and the blessed lock file.

In this model, the result of not running shutdown handlers will be to leave 1 instance of each version of the library in place; however, it will be discovered by future processes so that the system will not collect multiple unpacked copies of the same version.

The rocksdbjni jar unpacks its library file to temporay space if it is not available on the library load path. In pathological cases, large numbers of these files build up;
In particular when processes are killed, rather than being allowed to deleteOnExit the libraries.

Our solution is a mechanism to let a single instance of the file be shared by all the processes that want to use it. It’s not perfect, but the worst case should only result in a small fixed number of instances of the file being left in temporary space.
Method `searchOrCreate()` makes standard idiom use of `SharedTempFile` easier
Cleanup after themselves (as much as possible) for the multiprocess tests
Renamed test to something more appropriate (SharedTempFileLoaderTest)
Added to the list of tests in both make and cmake builds
Unlocking the shared temp file is often run as a cleanup operation when a JVM terminates.
It’s possible for instance that other cleanup operations may have removed the directory containing the shared temp file (e.g. junit cleanup).
So check for the locking directory’s existence, and quietly do nothing if it is not there.
Put our temporaries inside JUnit temporary when we can
Rationalise passing of parameters to test processes (add ArgUtil)
Create our SharedTempFile directory filenames with a digest of a supplied string as a suffix. This will typically be the full name of the library, including version. So when the version changes, the digest will change and the correct library will be picked up,
creating a new SharedTempFile directory on disk..
Add a variant shared library loader test that verifies the shared library can be loaded from java.library.path if it exists there; supported by adding a classpath parameter to the process creation code, so we can run a variant of our tests which is unable to find the JAR, but succeeds anyway because it does find the shared library directly.
@alanpaxton alanpaxton marked this pull request as draft January 20, 2025 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants