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)