8283060: RawNativeLibraries should allow multiple clients to load/unload the same library

Reviewed-by: sundar, jvernee, jpai
This commit is contained in:
Mandy Chung 2022-03-31 00:51:11 +00:00
parent 835c7e8d6d
commit 1ddab6fe4e
3 changed files with 56 additions and 23 deletions

View File

@ -31,8 +31,8 @@ import java.lang.invoke.MethodHandles;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
@ -45,7 +45,7 @@ import java.util.concurrent.ConcurrentHashMap;
* 3. No relationship with class loaders.
*/
public final class RawNativeLibraries {
final Map<String, RawNativeLibraryImpl> libraries = new ConcurrentHashMap<>();
final Set<RawNativeLibraryImpl> libraries = ConcurrentHashMap.newKeySet();
final Class<?> caller;
private RawNativeLibraries(MethodHandles.Lookup trustedCaller) {
@ -70,6 +70,12 @@ public final class RawNativeLibraries {
* Load a native library from the given path. Returns null if the given
* library is determined to be non-loadable, which is system-dependent.
*
* The library is opened with the platform-specific library loading
* mechanism. If this method is called with the same path multiple times,
* the library is opened the same number of times. To close the library
* of the given path, {@code #unload} must be called on all the
* {@code NativeLibrary} instances that load it.
*
* @param path the path of the native library
*/
@SuppressWarnings("removal")
@ -106,28 +112,39 @@ public final class RawNativeLibraries {
* NativeLibrary lib = libs.load(System.mapLibraryName("blas"));
* }
*
* The library is opened with the platform-specific library loading
* mechanism. If this method is called with the same pathname multiple times,
* the library is opened the same number of times. To close the library
* of the given path, {@code #unload} must be called on all the
* {@code NativeLibrary} instances that load it.
*
* @param pathname the pathname of the native library
* @see System#mapLibraryName(String)
*/
public NativeLibrary load(String pathname) {
return libraries.computeIfAbsent(pathname, this::get);
}
private RawNativeLibraryImpl get(String pathname) {
RawNativeLibraryImpl lib = new RawNativeLibraryImpl(caller, pathname);
RawNativeLibraryImpl lib = new RawNativeLibraryImpl(pathname);
if (!lib.open()) {
return null;
}
libraries.add(lib);
return lib;
}
/*
* Unloads the given native library.
* Unloads the given native library. Each {@code NativeLibrary}
* instance can be unloaded only once.
*
* The native library may remain opened after this method is called.
* Refer to the platform-specific library loading mechanism, for example,
* dlopen/dlclose on Unix or LoadLibrary/FreeLibrary on Windows.
*
* @throws IllegalArgumentException if the given library is not
* loaded by this RawNativeLibraries or has already been unloaded
*/
public void unload(NativeLibrary lib) {
Objects.requireNonNull(lib);
if (!libraries.remove(lib.name(), lib)) {
throw new IllegalArgumentException(lib.name() + " not loaded by this RawNativeLibraries instance");
if (!libraries.remove(lib)) {
throw new IllegalArgumentException("can't unload " + lib.name() + " loaded from " + lib);
}
RawNativeLibraryImpl nl = (RawNativeLibraryImpl)lib;
nl.close();
@ -139,7 +156,7 @@ public final class RawNativeLibraries {
// opaque handle to raw native library, used in native code.
long handle;
RawNativeLibraryImpl(Class<?> fromClass, String name) {
RawNativeLibraryImpl(String name) {
this.name = name;
}

View File

@ -45,17 +45,15 @@ public class Main {
NativeLibrariesTest test = new NativeLibrariesTest();
test.runTest();
try {
System.loadLibrary(NativeLibrariesTest.LIB_NAME);
} catch (UnsatisfiedLinkError e) { e.printStackTrace(); }
// unload the native library and then System::loadLibrary should succeed
test.unload();
// System::loadLibrary succeeds even the library is loaded as raw library
System.loadLibrary(NativeLibrariesTest.LIB_NAME);
// expect NativeLibraries to succeed even the library has been loaded by System::loadLibrary
test.loadTestLibrary();
// unload all NativeLibrary instances
test.unload();
// load zip library from JDK
test.load(System.mapLibraryName("zip"), true /* succeed */);

View File

@ -30,6 +30,8 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
public class NativeLibrariesTest implements Runnable {
public static final String LIB_NAME = "nativeLibrariesTest";
@ -52,6 +54,7 @@ public class NativeLibrariesTest implements Runnable {
}
private final RawNativeLibraries nativeLibraries;
private final Set<NativeLibrary> loadedLibraries = new HashSet<>();
public NativeLibrariesTest() {
this.nativeLibraries = RawNativeLibraries.newInstance(MethodHandles.lookup());
@ -74,7 +77,7 @@ public class NativeLibrariesTest implements Runnable {
NativeLibrary nl1 = nativeLibraries.load(lib);
NativeLibrary nl2 = nativeLibraries.load(lib);
assertTrue(nl1 != null && nl2 != null, "fail to load library");
assertTrue(nl1 == nl2, nl1 + " != " + nl2);
assertTrue(nl1 != nl2, "Expected different NativeLibrary instances");
assertTrue(loadedCount == 0, "Native library loaded. Expected: JNI_OnUnload not invoked");
assertTrue(unloadedCount == 0, "native library never unloaded");
@ -85,25 +88,40 @@ public class NativeLibrariesTest implements Runnable {
nativeLibraries.unload(nl1);
assertTrue(unloadedCount == 0, "Native library unloaded. Expected: JNI_OnUnload not invoked");
// reload the native library and expect new NativeLibrary instance
try {
nativeLibraries.unload(nl1);
throw new RuntimeException("Expect to fail as the library has already been unloaded");
} catch (IllegalArgumentException e) { }
// load the native library and expect new NativeLibrary instance
NativeLibrary nl3 = nativeLibraries.load(lib);
assertTrue(nl1 != nl3, nl1 + " == " + nl3);
assertTrue(loadedCount == 0, "Native library loaded. Expected: JNI_OnUnload not invoked");
// load successfully even from another loader
loadWithCustomLoader();
// keep the loaded NativeLibrary instances
loadedLibraries.add(nl2);
loadedLibraries.add(nl3);
}
/*
* Unloads all loaded NativeLibrary instance
*/
public void unload() {
NativeLibrary nl = nativeLibraries.load(libraryPath());
// unload the native library
nativeLibraries.unload(nl);
assertTrue(unloadedCount == 0, "Native library unloaded. Expected: JNI_OnUnload not invoked");
System.out.println("Unloading " + loadedLibraries.size() + " NativeLibrary instances");
for (NativeLibrary nl : loadedLibraries) {
nativeLibraries.unload(nl);
assertTrue(unloadedCount == 0, "Native library unloaded. Expected: JNI_OnUnload not invoked");
}
loadedLibraries.clear();
}
public void loadTestLibrary() {
NativeLibrary nl = nativeLibraries.load(libraryPath());
assertTrue(nl != null, "fail to load " + libraryPath());
loadedLibraries.add(nl);
}
public void load(String pathname, boolean succeed) {