Skip to content
Permalink
Browse files
8283060: RawNativeLibraries should allow multiple clients to load/unl…
…oad the same library

Reviewed-by: sundar, jvernee, jpai
  • Loading branch information
Mandy Chung committed Mar 31, 2022
1 parent 835c7e8 commit 1ddab6fe4e3c0c2068618135895dfde3a03b2ca3
Showing 3 changed files with 56 additions and 23 deletions.
@@ -31,8 +31,8 @@
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 @@
* 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 static RawNativeLibraries newInstance(MethodHandles.Lookup trustedCaller)
* 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 String run() {
* 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 @@ static class RawNativeLibraryImpl extends NativeLibrary {
// opaque handle to raw native library, used in native code.
long handle;

RawNativeLibraryImpl(Class<?> fromClass, String name) {
RawNativeLibraryImpl(String name) {
this.name = name;
}

@@ -45,17 +45,15 @@ public static void main(String... args) throws Exception {
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 */);

@@ -30,6 +30,8 @@
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 @@ static void nativeLibraryUnloaded() {
}

private final RawNativeLibraries nativeLibraries;
private final Set<NativeLibrary> loadedLibraries = new HashSet<>();

public NativeLibrariesTest() {
this.nativeLibraries = RawNativeLibraries.newInstance(MethodHandles.lookup());
@@ -74,7 +77,7 @@ public void runTest() throws Exception {
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 void runTest() throws Exception {
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) {

1 comment on commit 1ddab6f

@openjdk-notifier
Copy link

@openjdk-notifier openjdk-notifier bot commented on 1ddab6f Mar 31, 2022

Choose a reason for hiding this comment

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

Please sign in to comment.