Lantao Jin created CALCITE-7872:
-----------------------------------

             Summary: RelCollations and RelCollationImpl can deadlock during 
class initialization
                 Key: CALCITE-7872
                 URL: https://issues.apache.org/jira/browse/CALCITE-7872
             Project: Calcite
          Issue Type: Bug
          Components: core
    Affects Versions: 1.42.0
            Reporter: Lantao Jin


h2. Problem

The static initializers of {{RelCollations}} and {{RelCollationImpl}} depend on 
each other. The static initializer of {{RelCollations}} creates 
{{RelCollationImpl}} instances for {{RelCollations.EMPTY}} and 
{{RelCollations.PRESERVE}}. The static initializer of {{RelCollationImpl}} 
reads those two fields for its deprecated aliases {{RelCollationImpl.EMPTY}} 
and {{RelCollationImpl.PRESERVE}}. This has two effects:

# If one thread initializes {{RelCollations}} while another thread initializes 
{{RelCollationImpl}}, each thread waits forever for the class initialization 
lock that the other holds.
# If {{RelCollations}} is initialized first, which is the usual order, the 
static initializer of {{RelCollationImpl}} runs inside it and copies the two 
fields while they are still null. {{RelCollationImpl.EMPTY}} and 
{{RelCollationImpl.PRESERVE}} are then null for the life of the JVM.

h2. How to reproduce

A full {{./gradlew :core:test}} run hung for 30 minutes. The thread dump shows 
that two JUnit worker threads initialized {{CollationConversionTest}} and 
{{TraitPropagationTest}} at the same time. The static initializer of 
{{CollationConversionTest}} creates a subclass of {{RelCollationImpl}}, and the 
static initializer of {{TraitPropagationTest}} calls {{RelCollations.of}}. 
Thirty other test threads were blocked behind them, waiting for 
{{RelCollations}}:

{noformat}
"ForkJoinPool-1-worker-26" ...
   java.lang.Thread.State: RUNNABLE
        at 
org.apache.calcite.rel.RelCollationImpl.<clinit>(RelCollationImpl.java:46)
        - waiting on the Class initialization monitor for 
org.apache.calcite.rel.RelCollations
        at 
org.apache.calcite.plan.volcano.CollationConversionTest.<clinit>(CollationConversionTest.java:59)

"ForkJoinPool-1-worker-31" ...
   java.lang.Thread.State: RUNNABLE
        at org.apache.calcite.rel.RelCollations.<clinit>(RelCollations.java:45)
        - waiting on the Class initialization monitor for 
org.apache.calcite.rel.RelCollationImpl
        at 
org.apache.calcite.plan.volcano.TraitPropagationTest.<clinit>(TraitPropagationTest.java:96)
{noformat}

The deadlock is reliable if the two classes start uninitialized and two threads 
start together. I gave each trial a new class loader and a {{CyclicBarrier}}, 
and one thread called {{Class.forName}} for each class. All 40 of 40 trials 
deadlocked on {{main}}.

The null constants need only one thread, in a JVM that has not yet initialized 
either class:

{code:java}
RelCollation empty = RelCollations.EMPTY;
System.out.println(RelCollationImpl.EMPTY);    // prints "null"
System.out.println(RelCollationImpl.PRESERVE); // prints "null"
{code}

The aliases have referred to {{RelCollations}} since 2015 (commit e0b3c4a47, 
"Restore more of the methods removed in [CALCITE-572]").



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to