On Mon, 15 Mar 2021 17:15:59 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)
>
>> 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 me, at least, the holder pattern is clearer. I'm happy with that 
> approach.   ( I don't have an objection to the alternative, just a mild 
> preference for the holder )

Hello Chris, using the holder pattern sounds fine to me then. I've updated this 
PR accordingly. The test continues to pass with this fix.

Peter, I didn't get a chance to try out the `@Stable` for the previous approach 
but given that we decided to use this holder pattern, we will no longer need 
the performance tweaks.

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

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

Reply via email to