[
https://issues.apache.org/jira/browse/JDO-856?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18048427#comment-18048427
]
Tilmann Zäschke edited comment on JDO-856 at 12/30/25 8:03 PM:
---------------------------------------------------------------
+*Summary*+
It appears that the initialization of Q-classes in the reference implementation
is not thread-safe.
Reproduce:
The likelihood of the problem occuring correlates with the number of CPU cores
of the test environment.
On a is only reproducible on Multicore CPU. It is reliably reproducible on my
AMD Ryzen 7 7700 (8 cores / 16 Threads).
The minimum configuration is running the following two tests, where it will
hang indefinitely in the second test:
{{org.apache.jdo.tck.query.api.QueryExtentions \
org.apache.jdo.tck.query.api.SampleReadQueries}}
JUnit appears to be starting about 20 worker threads.
+*Analysis*+
I created stack trace dumps from two hanging runs, they are attached, see
[^tck-1-reduced.txt] and [^tck-2-reduced.txt]. I removed all unrelated stack
entries outside the calls to the test methods.
The stack traces show that 18 threads hang while trying to call the
candidate(...) method of a Q class. Two threads hang inside the Q-classes,
inside a chain of constructor calls, while calling the static initialization of
another Q-class.
Static initialization of classes is implicitly synchronized on the class
object. It appears that the concurrent calling of class initialization causes a
dead-lock.
+*Workaround*+
A workaround is to perform static initialization before executing tests. As
expected, the following changes (calling candidate() for each class) to the
SampleReadQueries test fixes the problem:
{{{}{color:#cc7832}protected void {color}{color:#ffc66d}localSetUp{color}()
{{}}}{{{{}}{}}}
{{ addTearDownClass(CompanyModelReader.getTearDownClasses()){color:#cc7832};
{color}{color:#cc7832}
{color}loadAndPersistCompanyModel(getPM()){color:#cc7832};
{color}{color:#cc7832} {color}QCompany.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QDentalInsurance.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QDepartment.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QEmployee.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QFullTimeEmployee.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QInsurance.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QMedicalInsurance.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QMeetingRoom.candidate(){color:#cc7832};
{color}}}{{{color:#cc7832} {color}QPerson.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QProject.candidate(){color:#cc7832};
{{}}}
+*Proposed solutions*+
1) We could try to introduce some kind of locking, for example, all
constructors could move their initialization (calling other constructors) into
a synchronized block that synchronizes on a common object, for example on the
ExpressionImpl.class object:
synchronized {color}(ExpressionImpl.{color:#cc7832}class{color}) {}}
{{{color:#9876aa} // call other Q-classes here{color}}}
{{}}}
{{This would be relatively easy to implement, but would require (multiple)
thread synchronization(s) whenever a Q-class is instantiated. This synchronized
block would need to go into every constructor in the class hierarchy.}}
2) Another solution might be to have candidate() only return a simple instance
of the desired class. The query engine would need to be modified to create/fill
in Q-classes into fields on demand while traversing the query result. It is not
clear whether this is possible and would require a significant refactoring.
was (Author: tilmannz):
+*Summary*+
It appears that the initialization of Q-classes in the reference implementation
is not thread-safe.
Reproduce:
The likelihood of the problem occuring correlates with the number of CPU cores
of the test environment.
On a is only reproducible on Multicore CPU. It is reliably reproducible on my
AMD Ryzen 7 7700 (8 cores / 16 Threads).
The minimum configuration is running the following two tests, where it will
hang indefinitely in the second test:
{{org.apache.jdo.tck.query.api.QueryExtentions \
org.apache.jdo.tck.query.api.SampleReadQueries}}
JUnit appears to be starting about 20 worker threads.
+*Analysis*+
I created stack trace dumps from two hanging runs, they are attached, see
[^tck-1-reduced.txt] and [^tck-2-reduced.txt]. I removed all unrelated stack
entries outside the calls to the test methods.
The stack traces show that 18 threads hang while trying to call the
candidate(...) method of a Q class. Two threads hang inside the Q-classes,
inside a chain of constructor calls, while calling the static initialization of
another Q-class.
Static initialization of classes is implicitly synchronized on the class
object. It appears that the concurrent calling of class initialization causes a
dead-lock.
+*Workaround*+
A workaround is to perform static initialization before executing tests. As
expected, the following changes (calling candidate() for each class) to the
SampleReadQueries test fixes the problem:
{{{}{color:#cc7832}protected void {color}{color:#ffc66d}localSetUp{color}()
{{}}}{{{}{}}}
{{ addTearDownClass(CompanyModelReader.getTearDownClasses()){color:#cc7832};
{color}{color:#cc7832}
{color}loadAndPersistCompanyModel(getPM()){color:#cc7832};
{color}{color:#cc7832} {color}QCompany.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QDentalInsurance.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QDepartment.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QEmployee.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QFullTimeEmployee.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QInsurance.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QMedicalInsurance.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QMeetingRoom.candidate(){color:#cc7832};
{color}}}{{{color:#cc7832} {color}QPerson.candidate(){color:#cc7832};
{color}{color:#cc7832} {color}QProject.candidate(){color:#cc7832};
{{}}}
+*Proposed solutions*+
1) We could try to introduce some kind of locking, for example, all
constructors could move their initialization (calling other constructors) into
a synchronized block that synchronizes on a common object, for example on the
ExpressionImpl.class object:
synchronized {color}(ExpressionImpl.{color:#cc7832}class{color}) {}}
{{{color:#9876aa} // call other Q-classes here{color}}}
{{}}}
{{This would be relatively easy to implement, but would require (multiple)
thread synchronization(s) whenever a Q-class is instantiated. This synchronized
block would need to go into every constructor in the class hierarchy.}}
2) Another solution might be to have candidate() only return a simple instance
of the desired class. The query engine would need to be modified to create/fill
in Q-classes into fields on demand while traversing the query result. It is not
clear whether this is possible and would require a significant refactoring.
> TCK hangs on query.conf
> -----------------------
>
> Key: JDO-856
> URL: https://issues.apache.org/jira/browse/JDO-856
> Project: JDO
> Issue Type: Bug
> Components: tck
> Affects Versions: JDO 3.2.1
> Reporter: Tilmann Zäschke
> Priority: Major
> Attachments: tck-1-reduced.txt, tck-2-reduced.txt
>
>
> Running the TCK on my machine (windows 11, Java 1.8), consistently hangs in
> query.conf.
> The tests do not hang on Ubuntu 22 on the same machine.
> The issue can be fixed by disabling some of the concurrent executions.
> Reproduce:
> {{mvn install -Djdo.tck.cfglist="query.conf"}}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)