On Fri, 12 Mar 2021 14:53:45 GMT, Chris Hegarty <che...@openjdk.org> wrote:

> What you have is probably fine, but has any consideration been given to using 
> the initialization-on-demand holder idiom? e.g.
> 
> ```
>  static private class CanonicalMapHolder {
>     static final Map<MethodHandleDesc, Function<DynamicConstantDesc<?>, 
> ConstantDesc>> CANONICAL_MAP
>           = Map.ofEntries(..);
>  }
> ```

Hello Chris,

Although I had thought of some other alternate ways to fix this, this idiom 
isn't something that I had thought of. Now that you showed this, I thought 
about it a bit more and it does look a lot more "natural" to read and maintain 
as compared to the current changes in this PR which does some juggling to avoid 
this deadlock. However, having thought about your proposed solution, I think 
_in theory_ it still leaves open the possibility of a deadlock.

Here's what the patch looks like with your suggested change:

diff --git 
a/src/java.base/share/classes/java/lang/constant/DynamicConstantDesc.java 
b/src/java.base/share/classes/java/lang/constant/DynamicConstantDesc.java
index 976b09e5665..c7bdcf4ce32 100644
--- a/src/java.base/share/classes/java/lang/constant/DynamicConstantDesc.java
+++ b/src/java.base/share/classes/java/lang/constant/DynamicConstantDesc.java
@@ -64,8 +64,6 @@ public abstract class DynamicConstantDesc<T>
     private final String constantName;
     private final ClassDesc constantType;
 
-    private static Map<MethodHandleDesc, Function<DynamicConstantDesc<?>, 
ConstantDesc>> canonicalMap;
-
     /**
      * Creates a nominal descriptor for a dynamic constant.
      *
@@ -274,25 +272,7 @@ public abstract class DynamicConstantDesc<T>
     }
 
     private ConstantDesc tryCanonicalize() {
-        var canonDescs = canonicalMap;
-        if (canonDescs == null) {
-            canonDescs = Map.ofEntries(
-                    Map.entry(ConstantDescs.BSM_PRIMITIVE_CLASS, 
DynamicConstantDesc::canonicalizePrimitiveClass),
-                    Map.entry(ConstantDescs.BSM_ENUM_CONSTANT, 
DynamicConstantDesc::canonicalizeEnum),
-                    Map.entry(ConstantDescs.BSM_NULL_CONSTANT, 
DynamicConstantDesc::canonicalizeNull),
-                    Map.entry(ConstantDescs.BSM_VARHANDLE_STATIC_FIELD, 
DynamicConstantDesc::canonicalizeStaticFieldVarHandle),
-                    Map.entry(ConstantDescs.BSM_VARHANDLE_FIELD, 
DynamicConstantDesc::canonicalizeFieldVarHandle),
-                    Map.entry(ConstantDescs.BSM_VARHANDLE_ARRAY, 
DynamicConstantDesc::canonicalizeArrayVarHandle));
-            synchronized (DynamicConstantDesc.class) {
-                if (canonicalMap == null) {
-                    canonicalMap = canonDescs;
-                } else {
-                    canonDescs = canonicalMap;
-                }
-            }
-        }
-
-        Function<DynamicConstantDesc<?>, ConstantDesc> f = 
canonDescs.get(bootstrapMethod);
+        Function<DynamicConstantDesc<?>, ConstantDesc> f = 
CanonicalMapHolder.CANONICAL_MAP.get(bootstrapMethod);
         if (f != null) {
             try {
                 return f.apply(this);
@@ -405,4 +385,15 @@ public abstract class DynamicConstantDesc<T>
             super(bootstrapMethod, constantName, constantType, bootstrapArgs);
         }
     }
+
+    private static final class CanonicalMapHolder {
+        static final Map<MethodHandleDesc, Function<DynamicConstantDesc<?>, 
ConstantDesc>> CANONICAL_MAP =
+                Map.ofEntries(
+                    Map.entry(ConstantDescs.BSM_PRIMITIVE_CLASS, 
DynamicConstantDesc::canonicalizePrimitiveClass),
+                    Map.entry(ConstantDescs.BSM_ENUM_CONSTANT, 
DynamicConstantDesc::canonicalizeEnum),
+                    Map.entry(ConstantDescs.BSM_NULL_CONSTANT, 
DynamicConstantDesc::canonicalizeNull),
+                    Map.entry(ConstantDescs.BSM_VARHANDLE_STATIC_FIELD, 
DynamicConstantDesc::canonicalizeStaticFieldVarHandle),
+                    Map.entry(ConstantDescs.BSM_VARHANDLE_FIELD, 
DynamicConstantDesc::canonicalizeFieldVarHandle),
+                    Map.entry(ConstantDescs.BSM_VARHANDLE_ARRAY, 
DynamicConstantDesc::canonicalizeArrayVarHandle));
+    }
 }


Please consider the following, where I try and explain the theoretical 
possibility of a deadlock with this approach:

1. Let's consider 2 threads T1 and T2 doing concurrent execution

2. Let's for a moment assume that neither `DynamicConstantDesc` nor 
`ConstantDescs` classes have been initialized.

3. T1 does a call to `DynamicConstantDesc.ofCanonical(...)` and T2 does a call 
to something/anything on `ConstantDescs`, which triggers a class initialization 
of `ConstantDescs`.

4. T1 (which called `DynamicConstantDesc.ofCanonical(...)`) will reach the 
`tryCanonicalize` and will access `CanonicalMapHolder.CANONICAL_MAP` in the 
implementation of that method. Because of this access (and since 
`CanonicalMapHolder` hasn't yet been initialized), T1 will obtain (an implicit) 
lock on the `CanonicalMapHolder` class (for the class initialization). Let's 
assume T1 is granted this lock on `CanonicalMapHolder` class and it goes ahead 
into the static block of that holder class to do the initialization of 
`CANONICAL_MAP`. To do so, because of the code in the static block of 
`CanonicalMapHolder`, it now requires a class initialization lock on 
`ConstantDescs` (since `ConstantDescs` hasn't yet been initialized). So it 
requests for that lock on `ConstantDescs` class.

5. Concurrently, let's say T2 has initiated a class initialization of 
`ConstantDescs`. So T2 is currently holding a class initialization lock on 
`ConstantDescs`. While the static initialization of `ConstantDescs` is going 
on, let's assume (in theory), due to the whole lot of code in the static 
initialization of `ConstantDescs`, it either directly or indirectly ends up 
calling `DynamicConstantDesc.ofCanonical(...)`. This then means that T2 now 
enters the `tryCanonicalize` and accesses `CanonicalMapHolder.CANONICAL_MAP` 
and since `CanonicalMapHolder` hasn't yet been (fully) initialized (remember T1 
is still in the static block of `CanonicalMapHolder`), it tries to obtain a 
class initialization lock on `CanonicalMapHolder` which is currently held by 
T1. T1 won't let go that lock since it wants the lock on `ConstantDescs` which 
is currently held by T2. This effectively ends up triggering the deadlock.

This deadlock isn't possible with the current approach that this PR has.

I want to clarify that, the deadlock that I explain above with your proposed 
approach is just a theoretical possibility. The `ConstantDescs` doesn't 
currently have any direct call to `DynamicConstantDesc.ofCanonical` in its 
static initialization nor can I see or think of any indirect calls to 
`DynamicConstantDesc.ofCanonical` from its static block. I need input from you 
whether I should keep aside this theoretic issue and deal with it if/when we 
run into it (the test case in this PR, IMO, is robust enough to catch that 
deadlock if/when it happens due to any future code changes in this area). If 
you and others think that we can ignore this case, then your proposed approach 
of using this lazy holder for initialization, IMO, is much cleaner and natural 
to read and I will go ahead and update this PR to use it.

For those interested in seeing this potential theoretical deadlock with the 
lazy holder approach in action, I just created a separate branch here 
https://github.com/jaikiran/jdk/commits/8263108-demo-deadlock-theory which 
consistently reproduces the deadlock when the `DynamicConstantDescTest` jtreg 
test is run. The crucial part is that I introduced the following (dummy) call 
in `ConstantDescs`:

diff --git a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java 
b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java
index 3a000d1bd4a..3f793f5a8b3 100644
--- a/src/java.base/share/classes/java/lang/constant/ConstantDescs.java
+++ b/src/java.base/share/classes/java/lang/constant/ConstantDescs.java
@@ -286,6 +286,13 @@ public final class ConstantDescs {
     static final DirectMethodHandleDesc MHD_METHODHANDLE_ASTYPE
             = MethodHandleDesc.ofMethod(Kind.VIRTUAL, CD_MethodHandle, 
"asType",
                                         MethodTypeDesc.of(CD_MethodHandle, 
CD_MethodType));
+
+    static {
+        // just a dummy call to DynamicConstantDesc.ofCanonical
+        DynamicConstantDesc.ofCanonical(ConstantDescs.BSM_ENUM_CONSTANT, 
"SHOW_REFLECT_FRAMES",
+                ClassDesc.of("java.lang.StackWalker").nested("Option"), new 
ConstantDesc[0]);
+    }
+


The following thread dump shows the deadlock with that lazy holder approach:

"MainThread" #15 prio=5 os_prio=31 cpu=6.26ms elapsed=120.18s 
tid=0x00007fe0e58df800 nid=0x5e03 waiting on condition  [0x000070000d71d000]
   java.lang.Thread.State: WAITING (parking)
        at jdk.internal.misc.Unsafe.park(java.base@17-internal/Native Method)
        - parking to wait for  <0x000000070ff65d90> (a 
java.util.concurrent.FutureTask)
        at 
java.util.concurrent.locks.LockSupport.park(java.base@17-internal/LockSupport.java:211)
        at 
java.util.concurrent.FutureTask.awaitDone(java.base@17-internal/FutureTask.java:447)
        at 
java.util.concurrent.FutureTask.get(java.base@17-internal/FutureTask.java:190)
        at DynamicConstantDescTest.main(DynamicConstantDescTest.java:80)
        at 
jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@17-internal/Native
 Method)
        at 
jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@17-internal/NativeMethodAccessorImpl.java:78)
        at 
jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@17-internal/DelegatingMethodAccessorImpl.java:43)
        at 
java.lang.reflect.Method.invoke(java.base@17-internal/Method.java:566)
        at 
com.sun.javatest.regtest.agent.MainWrapper$MainThread.run(MainWrapper.java:127)
        at java.lang.Thread.run(java.base@17-internal/Thread.java:831)

"pool-1-thread-1" #16 prio=5 os_prio=31 cpu=14.32ms elapsed=120.18s 
tid=0x00007fe0e58dea00 nid=0x9903 waiting on condition  [0x000070000d820000]
   java.lang.Thread.State: WAITING (parking)
        at jdk.internal.misc.Unsafe.park(java.base@17-internal/Native Method)
        - parking to wait for  <0x000000070ff59d30> (a 
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at 
java.util.concurrent.locks.LockSupport.park(java.base@17-internal/LockSupport.java:341)
        at 
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionNode.block(java.base@17-internal/AbstractQueuedSynchronizer.java:506)
        at 
java.util.concurrent.ForkJoinPool.unmanagedBlock(java.base@17-internal/ForkJoinPool.java:3454)
        at 
java.util.concurrent.ForkJoinPool.managedBlock(java.base@17-internal/ForkJoinPool.java:3425)
        at 
java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@17-internal/AbstractQueuedSynchronizer.java:1623)
        at 
java.util.concurrent.LinkedBlockingQueue.take(java.base@17-internal/LinkedBlockingQueue.java:435)
        at 
java.util.concurrent.ThreadPoolExecutor.getTask(java.base@17-internal/ThreadPoolExecutor.java:1061)
        at 
java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17-internal/ThreadPoolExecutor.java:1121)
        at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17-internal/ThreadPoolExecutor.java:635)
        at java.lang.Thread.run(java.base@17-internal/Thread.java:831)

"pool-1-thread-2" #17 prio=5 os_prio=31 cpu=7.88ms elapsed=120.18s 
tid=0x00007fe0e4012c00 nid=0x6003 in Object.wait()  [0x000070000d923000]
   java.lang.Thread.State: RUNNABLE
        at 
java.lang.constant.DynamicConstantDesc.tryCanonicalize(java.base@17-internal/DynamicConstantDesc.java:275)
        - waiting on the Class initialization monitor for 
java.lang.constant.DynamicConstantDesc$CanonicalMapHolder
        at 
java.lang.constant.DynamicConstantDesc.ofCanonical(java.base@17-internal/DynamicConstantDesc.java:136)
        at 
DynamicConstantDescTest$InvokeOfCanonical.call(DynamicConstantDescTest.java:129)
        at 
java.util.concurrent.FutureTask.run(java.base@17-internal/FutureTask.java:264)
        at 
java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17-internal/ThreadPoolExecutor.java:1135)
        at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17-internal/ThreadPoolExecutor.java:635)
        at java.lang.Thread.run(java.base@17-internal/Thread.java:831)

"pool-1-thread-3" #18 prio=5 os_prio=31 cpu=15.43ms elapsed=120.18s 
tid=0x00007fe0e4070600 nid=0x9803 in Object.wait()  [0x000070000da26000]
   java.lang.Thread.State: RUNNABLE
        at 
java.lang.constant.DynamicConstantDesc.tryCanonicalize(java.base@17-internal/DynamicConstantDesc.java:275)
        - waiting on the Class initialization monitor for 
java.lang.constant.DynamicConstantDesc$CanonicalMapHolder
        at 
java.lang.constant.DynamicConstantDesc.ofCanonical(java.base@17-internal/DynamicConstantDesc.java:136)
        at 
java.lang.constant.ConstantDescs.<clinit>(java.base@17-internal/ConstantDescs.java:292)
        at java.lang.Class.forName0(java.base@17-internal/Native Method)
        at java.lang.Class.forName(java.base@17-internal/Class.java:375)
        at DynamicConstantDescTest$Task.call(DynamicConstantDescTest.java:104)
        at DynamicConstantDescTest$Task.call(DynamicConstantDescTest.java:87)
        at 
java.util.concurrent.FutureTask.run(java.base@17-internal/FutureTask.java:264)
        at 
java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17-internal/ThreadPoolExecutor.java:1135)
        at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17-internal/ThreadPoolExecutor.java:635)
        at java.lang.Thread.run(java.base@17-internal/Thread.java:831)

"pool-1-thread-4" #19 prio=5 os_prio=31 cpu=5.39ms elapsed=120.18s 
tid=0x00007fe0e4070c00 nid=0x9603 in Object.wait()  [0x000070000db29000]
   java.lang.Thread.State: RUNNABLE
        at 
java.lang.constant.DynamicConstantDesc$CanonicalMapHolder.<clinit>(java.base@17-internal/DynamicConstantDesc.java:390)
        - waiting on the Class initialization monitor for 
java.lang.constant.ConstantDescs
        at 
java.lang.constant.DynamicConstantDesc.tryCanonicalize(java.base@17-internal/DynamicConstantDesc.java:275)
        at 
java.lang.constant.DynamicConstantDesc.ofCanonical(java.base@17-internal/DynamicConstantDesc.java:136)
        at 
DynamicConstantDescTest$InvokeOfCanonical.call(DynamicConstantDescTest.java:129)
        at 
java.util.concurrent.FutureTask.run(java.base@17-internal/FutureTask.java:264)
        at 
java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@17-internal/ThreadPoolExecutor.java:1135)
        at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@17-internal/ThreadPoolExecutor.java:635)
        at java.lang.Thread.run(java.base@17-internal/Thread.java:831)

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

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

Reply via email to