On Thu, 27 May 2021 14:31:59 GMT, Aleksei Voitylov <avoity...@openjdk.org> 
wrote:

>> Please review this PR which fixes the deadlock in ClassLoader between the 
>> two lock objects - a lock object associated with the class being loaded, and 
>> the ClassLoader.loadedLibraryNames hash map, locked during the native 
>> library load operation.
>> 
>> Problem being fixed:
>> 
>> The initial reproducer demonstrated a deadlock between the JarFile/ZipFile 
>> and the hash map. That deadlock exists even when the ZipFile/JarFile lock is 
>> removed because there's another lock object in the class loader, associated 
>> with the name of the class being loaded. Such objects are stored in 
>> ClassLoader.parallelLockMap. The deadlock occurs when JNI_OnLoad() loads 
>> exactly the same class, whose signature is being verified in another thread.
>> 
>> Proposed fix:
>> 
>> The proposed patch suggests to get rid of locking loadedLibraryNames hash 
>> map and synchronize on each entry name, as it's done with class names in see 
>> ClassLoader.getClassLoadingLock(name) method.
>> 
>> The patch introduces nativeLibraryLockMap which holds the lock objects for 
>> each library name, and the getNativeLibraryLock() private method is used to 
>> lazily initialize the corresponding lock object. nativeLibraryContext was 
>> changed to ThreadLocal, so that in any concurrent thread it would have a 
>> NativeLibrary object on top of the stack, that's being currently 
>> loaded/unloaded in that thread. nativeLibraryLockMap accumulates the names 
>> of all native libraries loaded - in line with class loading code, it is not 
>> explicitly cleared.
>> 
>> Testing:  jtreg and jck testing with no regressions. A new regression test 
>> was developed.
>
> Aleksei Voitylov has updated the pull request incrementally with one 
> additional commit since the last revision:
> 
>   address review comments

The change looks good in general with a minor suggestion to rename 
`NativeLibraryContext::get` to `NativeLibraryContext::current` which would be 
clearer as it returns the current native library context (of the current 
thread).

src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java line 207:

> 205:              *
> 206:              * We use a static stack to hold the list of libraries we are
> 207:              * loading, so that each thread maintains its own stack.

line 206: no longer a static stack.  line 206-207 can be replaced with:


             * Each thread maintains its own stack to hold the list of 
libraries it is loading.

src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java line 213:

> 211:              * a different class loader, we raise UnsatisfiedLinkError.
> 212:              */
> 213:             for (NativeLibraryImpl lib : NativeLibraryContext.get()) {

I suggest to rename the `get` method of `NativeLibraryContext` to `current` 
that returns the current thread's context.

test/jdk/java/lang/ClassLoader/loadLibraryDeadlock/LoadLibraryDeadlock.java 
line 49:

> 47:                          InstantiationException |
> 48:                          IllegalAccessException ignore) {
> 49:                     System.out.println("Class Class1 not found.");

In general a test should let the exception to propagate.   In this case, the 
threads (both t1 and t2) can warp the exception and throw `RuntimeException` as 
it's an error.

test/jdk/java/lang/ClassLoader/loadLibraryDeadlock/LoadLibraryDeadlock.java 
line 60:

> 58:                     System.out.println("Signed jar loaded.");
> 59:                 } catch (ClassNotFoundException ignore) {
> 60:                     System.out.println("Class Class2 not found.");

Same as above

test/jdk/java/lang/ClassLoader/loadLibraryDeadlock/TestLoadLibraryDeadlock.java 
line 28:

> 26:  * @test
> 27:  * @bug 8266310
> 28:  * @summary deadlock while loading the JNI code

please update `@summary` with a description of the test (rather than the 
synposis of the bug).

test/jdk/java/lang/ClassLoader/loadLibraryDeadlock/TestLoadLibraryDeadlock.java 
line 69:

> 67: 
> 68:     private static OutputAnalyzer genKey() throws Throwable {
> 69:         runCommandInTestClassPath("rm", "-f", KEYSTORE);

Can you use `jdk.test.lib.util.FileUtils` instead of exec `rm -f`.

test/jdk/java/lang/ClassLoader/loadLibraryDeadlock/TestLoadLibraryDeadlock.java 
line 84:

> 82: 
> 83:     private static OutputAnalyzer createJar(String outputJar, String... 
> classes) throws Throwable {
> 84:         String jar = JDKToolFinder.getJDKTool("jar");

You can consider using `jdk.test.lib.util.JarUtils` to reduce the test 
execution time so that it can create the jar without exec'ing another process.

test/jdk/java/lang/ClassLoader/loadLibraryDeadlock/TestLoadLibraryDeadlock.java 
line 141:

> 139:         try {
> 140:             return future.get(1000, TimeUnit.MILLISECONDS);
> 141:         } catch (Exception ignoreAll) {

if this is an unexpected error case, it should throw an exception.

test/jdk/java/lang/ClassLoader/loadLibraryDeadlock/TestLoadLibraryDeadlock.java 
line 165:

> 163: 
> 164:         runCommandInTestClassPath("rm", "-f", "*.jar")
> 165:                 .shouldHaveExitValue(0);

You can use `jdk.test.lib.util.FileUtils` to delete a directory or a given path.

test/jdk/java/lang/ClassLoader/loadLibraryUnload/LoadLibraryUnload.java line 31:

> 29:  * @test
> 30:  * @bug 8266310
> 31:  * @summary deadlock while loading the JNI code

please update `@summary` with a description of the test (rather than the 
synposis of the bug).

test/jdk/java/lang/ClassLoader/loadLibraryUnload/LoadLibraryUnload.java line 83:

> 81:                 this.object = fromClass.newInstance();
> 82:                 this.method = fromClass.getDeclaredMethod("loadLibrary");
> 83:             } catch (Exception error) {

Nit: `s/Exception/ReflectiveOperationException/` as ROE is the specific checked 
exception you want to catch here.

test/jdk/java/lang/ClassLoader/loadLibraryUnload/LoadLibraryUnload.java line 84:

> 82:                 this.method = fromClass.getDeclaredMethod("loadLibrary");
> 83:             } catch (Exception error) {
> 84:                 throw new Error(error);

Error is fine.  Most tests throw `RuntimeException` that can be another choice.

test/jdk/java/lang/ClassLoader/loadLibraryUnload/LoadLibraryUnload.java line 92:

> 90:             try {
> 91:                 method.invoke(object);
> 92:             } catch (Exception error) {

Same here to catch the `ReflectiveOperationException`.

test/jdk/java/lang/ClassLoader/loadLibraryUnload/LoadLibraryUnload.java line 
148:

> 146:         threads = null;
> 147:         exceptions.clear();
> 148:         waitForUnload(wClass);

`jdk.test.lib.util.ForceGC` can be used here.  You can reference 
test/jdk/java/lang/ClassLoader/nativeLIbrary/NativeLibraryTest.java.

test/jdk/java/lang/ClassLoader/loadLibraryUnload/LoadLibraryUnloadTest.java 
line 32:

> 30:  * @test
> 31:  * @bug 8266310
> 32:  * @summary deadlock while loading the JNI code

Please update `@summary` with a description of the test (rather than the 
synposis of the bug)

-------------

PR: https://git.openjdk.java.net/jdk/pull/3976

Reply via email to