Can I please get a review for this proposed patch for the issue reported in 
https://bugs.openjdk.java.net/browse/JDK-8263108?

As noted in that issue, the `java.lang.constant.DynamicConstantDesc` and 
`java.lang.constant.ConstantDescs` can end up in a classloading deadlock due to 
the nature of the code involved in their static blocks. A thread dump of one 
such deadlock (reproduced using the code attached to that issue) is as follows:

"Thread A" #14 prio=5 os_prio=31 cpu=101.45ms elapsed=8.30s 
tid=0x00007ff88e01ca00 nid=0x6003 in Object.wait()  [0x000070000a4c6000]
   java.lang.Thread.State: RUNNABLE
        at 
java.lang.constant.DynamicConstantDesc.<clinit>(java.base@16-ea/DynamicConstantDesc.java:67)
        - waiting on the Class initialization monitor for 
java.lang.constant.ConstantDescs
        at Deadlock.threadA(Deadlock.java:14)
        at Deadlock$$Lambda$1/0x0000000800c00a08.run(Unknown Source)
        at java.lang.Thread.run(java.base@16-ea/Thread.java:831)

"Thread B" #15 prio=5 os_prio=31 cpu=103.15ms elapsed=8.30s 
tid=0x00007ff88b936a00 nid=0x9b03 in Object.wait()  [0x000070000a5c9000]
   java.lang.Thread.State: RUNNABLE
        at 
java.lang.constant.ClassDesc.ofDescriptor(java.base@16-ea/ClassDesc.java:145)
        - waiting on the Class initialization monitor for 
java.lang.constant.DynamicConstantDesc
        at 
java.lang.constant.ConstantDescs.<clinit>(java.base@16-ea/ConstantDescs.java:239)
        at Deadlock.threadB(Deadlock.java:24)
        at Deadlock$$Lambda$2/0x0000000800c00c28.run(Unknown Source)
        at java.lang.Thread.run(java.base@16-ea/Thread.java:831)

The commit in this PR resolves that deadlock by moving the `canonicalMap` 
initialization in `DynamicConstantDesc`, from the static block, to a lazily 
initialized map, into the `tryCanonicalize` (private) method of the same class. 
That's the only method which uses this map.

The implementation in `tryCanonicalize` carefully ensures that the deadlock 
isn't shifted from the static block to this method, by making sure that the 
`synchronization` on `DynamicConstantDesc` in this method happens only when 
it's time to write the state to the shared `canonicalMap`. This has an 
implication that the method local variable `canonDescs` can get initialized by 
multiple threads unnecessarily but from what I can see, that's the only way we 
can avoid this deadlock. This penalty of multiple threads initializing and then 
throwing away that map isn't too huge because that will happen only once when 
the `canonicalMap` is being initialized for the first time.

An alternate approach that I thought of was to completely get rid of this 
shared cache `canonicalMap` and instead just use method level map (that gets 
initialized each time) in the `tryCanonicalize` method (thus requiring no 
locks/synchronization). I ran a JMH benchmark with the current change proposed 
in this PR and with the alternate approach of using the method level map. The 
performance number from that run showed that the approach of using the method 
level map has relatively big impact on performance (even when there's a cache 
hit in that method). So I decided to not pursue that approach. I'll include the 
benchmark code and the numbers in a comment in this PR, for reference.

The commit in this PR also includes a jtreg testcase which (consistently) 
reproduces the deadlock without the fix and passes when this fix is applied. 
Extra manual testing has been done to verify that the classes of interest 
(noted in that testcase) are indeed getting loaded in those concurrent threads 
and not in the main thread. For future maintainers, there's a implementation 
note added on that testcase explaining why it cannot be moved into testng test.

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

Commit messages:
 - 8263108: Class initialization deadlock in java.lang.constant

Changes: https://git.openjdk.java.net/jdk/pull/2893/files
 Webrev: https://webrevs.openjdk.java.net/?repo=jdk&pr=2893&range=00
  Issue: https://bugs.openjdk.java.net/browse/JDK-8263108
  Stats: 170 lines in 2 files changed: 160 ins; 7 del; 3 mod
  Patch: https://git.openjdk.java.net/jdk/pull/2893.diff
  Fetch: git fetch https://git.openjdk.java.net/jdk pull/2893/head:pull/2893

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

Reply via email to