This is an automated email from the ASF dual-hosted git repository.
andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git
The following commit(s) were added to refs/heads/main by this push:
new 8982eab3ce GH-2809: Integration tests for JenaSystem#init The test
code triggered a "Deadlock in JenaSystem.init()" (GH-2787
https://github.com/apache/jena/issues/2787) in Jena 5.2.0. --> It seems to be
stable now.
8982eab3ce is described below
commit 8982eab3ce8588eb8046705bedb5aa4b2190862d
Author: arne-bdt <[email protected]>
AuthorDate: Wed Oct 23 16:03:29 2024 +0200
GH-2809: Integration tests for JenaSystem#init
The test code triggered a "Deadlock in JenaSystem.init()" (GH-2787
https://github.com/apache/jena/issues/2787) in Jena 5.2.0.
--> It seems to be stable now.
---
jena-integration-tests/pom.xml | 17 +++
.../src/test/java/org/apache/jena/sys/TS_Sys.java | 30 +++++
.../java/org/apache/jena/sys/TestJenaSystem.java | 123 +++++++++++++++++++++
.../jena/sys/TestJenaSystemWithFreshJVM.java | 98 ++++++++++++++++
4 files changed, 268 insertions(+)
diff --git a/jena-integration-tests/pom.xml b/jena-integration-tests/pom.xml
index a6b25fded4..a3f441348d 100644
--- a/jena-integration-tests/pom.xml
+++ b/jena-integration-tests/pom.xml
@@ -152,6 +152,18 @@
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.openjdk.jmh</groupId>
+ <artifactId>jmh-generator-annprocess</artifactId>
+ <scope>test</scope>
+ </dependency>
+
</dependencies>
<build>
@@ -186,6 +198,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <compilerArgs>
+ <arg>-proc:full</arg>
+ </compilerArgs>
+ </configuration>
</plugin>
<plugin>
diff --git
a/jena-integration-tests/src/test/java/org/apache/jena/sys/TS_Sys.java
b/jena-integration-tests/src/test/java/org/apache/jena/sys/TS_Sys.java
new file mode 100644
index 0000000000..ce21341f10
--- /dev/null
+++ b/jena-integration-tests/src/test/java/org/apache/jena/sys/TS_Sys.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sys;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+@RunWith(Suite.class)
[email protected](
+ { TestJenaSystem.class
+ , TestJenaSystemWithFreshJVM.class
+ })
+
+public class TS_Sys { }
diff --git
a/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystem.java
b/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystem.java
new file mode 100644
index 0000000000..078740897c
--- /dev/null
+++
b/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystem.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jena.sys;
+
+import org.apache.jena.rdf.model.ModelFactory;
+import org.apache.jena.rdfconnection.RDFConnection;
+import org.apache.jena.rdfconnection.RDFConnectionFuseki;
+import org.junit.Test;
+
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.HashSet;
+import java.util.concurrent.Executors;
+import java.util.stream.IntStream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Tests for {@link JenaSystem#init()}.
+ * <p>
+ * These tests are placed in the integration tests module to ensure that the
initialization
+ * is successful when multiple modules are loaded.
+ * <p>
+ * These tests need to be run in a separate JVM to run with a not initialized
JenaSystem.
+ * This usually is the case when only the test method is directly executed in
the IDE.
+ * For the maven build, we call these test methods from {@link
TestJenaSystemWithFreshJVM}
+ * to ensure that the JVM is fresh.
+ */
+public class TestJenaSystem {
+
+ /**
+ * Tests that the first initialization of JenaSystem is successful.
+ * <p>
+ * For the maven build, we use this method in {@link
TestJenaSystemWithFreshJVM#init()}
+ * to ensure that the JVM is fresh.
+ */
+ @Test
+ public void init() {
+ assertDoesNotThrow(
+ () -> JenaSystem.init());
+ }
+
+ /**
+ * Tests that the first initialization of {@link JenaSystem} is even
successful when called in parallel.
+ * <p>
+ * This code was able to reproduce a deadlock for <a
href="https://github.com/apache/jena/issues/2787">GitHub issue 2787</a>,
+ * which is now fixed.
+ * <p>
+ * The deadlock was observed when {@link JenaSystem} was initialized in
parallel with
+ * {@link ModelFactory#createDefaultModel()} or other classes that use a
static initializer calling
+ * {@link JenaSystem#init}. This test is used to reproduce the deadlock
and ensure that it is fixed.
+ * <p>
+ * For the maven build, we use this method in {@link
org.apache.jena.sys.TestJenaSystemWithFreshJVM#initParallel()}
+ * to ensure that the JVM is fresh.
+ */
+ @Test
+ public void initParallel() {
+
+ var pool = Executors.newFixedThreadPool(2);
+ try {
+ var futures = IntStream.range(1, 3)
+ .mapToObj(i -> pool.submit(() -> {
+ if (i % 2 == 0) {
+ ModelFactory.createDefaultModel();
+ } else {
+ JenaSystem.init();
+ }
+
+ return i;
+ }))
+ .toList();
+
+ var intSet = new HashSet<Integer>();
+ assertTimeoutPreemptively(
+ Duration.of(4, ChronoUnit.SECONDS),
+ () -> {
+ for (var future : futures) {
+ intSet.add(future.get());
+ }
+ });
+
+ assertEquals(2, intSet.size());
+ } finally {
+ pool.shutdown();
+ }
+ }
+
+ /**
+ * This test ensures that the initialization of {@link
RDFConnectionFuseki} is successful.
+ * This test is located here because the initialization of {@link
RDFConnectionFuseki} depends on the proper
+ * initialization of {@link JenaSystem}.
+ * It is a regression test for <a
href="https://github.com/apache/jena/issues/2787">GitHub issue 2787</a>.
+ * <p>
+ * The issue was:
+ * When the static initialization of {@link JenaSystem} was missing in
{@link org.apache.jena.rdflink.RDFLinkHTTPBuilder},
+ * the initialization of {@link RDFConnectionFuseki} failed with a {@link
java.lang.ExceptionInInitializerError}
+ * caused by a {@link java.lang.NullPointerException}.
+ * <p>
+ * For the maven build, we use this method in {@link
TestJenaSystemWithFreshJVM#initRDFConnectionFuseki()}
+ */
+ @Test
+ public void initRDFConnectionFuseki() {
+ try (RDFConnection conn =
RDFConnectionFuseki.service("http://localhost:3030/ds").build()) {
+ assertTrue(true);
+ }
+ }
+}
diff --git
a/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystemWithFreshJVM.java
b/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystemWithFreshJVM.java
new file mode 100644
index 0000000000..1566b934a7
--- /dev/null
+++
b/jena-integration-tests/src/test/java/org/apache/jena/sys/TestJenaSystemWithFreshJVM.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jena.sys;
+
+import org.apache.jena.rdfconnection.RDFConnectionFuseki;
+import org.junit.Assert;
+import org.junit.Test;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+import org.openjdk.jmh.runner.options.TimeValue;
+import org.openjdk.jmh.runner.options.VerboseMode;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This test is not a benchmark but a test to ensure that the test methods
from {@link TestJenaSystem} are executed
+ * in a fresh JVM.
+ */
+@State(Scope.Benchmark)
+public class TestJenaSystemWithFreshJVM {
+
+ static final TestJenaSystem testJenaSystem = new TestJenaSystem();
+
+ /**
+ * Test that the first initialization of JenaSystem is successful when
called in parallel.
+ * <p>
+ * Executes test code of {@link TestJenaSystem#initParallel()}.
+ */
+ @Benchmark
+ public void initParallel() {
+ testJenaSystem.initParallel();
+ }
+
+ /**
+ * Test that the first initialization of JenaSystem is successful.
+ * <p>
+ * Executes test code of {@link TestJenaSystem#init()}.
+ */
+ @Benchmark
+ public void init() {
+ testJenaSystem.init();
+ }
+
+ /**
+ * This test ensures that the initialization of {@link
RDFConnectionFuseki} is successful.
+ * It is a regression test for <a
href="https://github.com/apache/jena/issues/2787">GitHub issue 2787</a>.
+ * <p>
+ * Executes test code of {@link TestJenaSystem#initRDFConnectionFuseki()}.
+ */
+ @Benchmark
+ public void initRDFConnectionFuseki() {
+ testJenaSystem.initRDFConnectionFuseki();
+ }
+
+ /**
+ * This test uses JMH to run the test methods from {@link TestJenaSystem}
in a fresh JVM.
+ * This is necessary for maven builds to ensure that the JVM is fresh.
+ */
+ @Test
+ public void initInFreshJVM() throws Exception {
+ var opt = new OptionsBuilder()
+ .include(this.getClass().getName())
+ .mode(Mode.SingleShotTime)
+ .verbosity(VerboseMode.SILENT)
+ .timeUnit(TimeUnit.SECONDS)
+ .warmupTime(TimeValue.NONE)
+ .warmupIterations(0) // we don't need warmup
+ .measurementIterations(1) // we only need one iteration
+ .measurementTime(TimeValue.NONE)
+ .threads(1) // we only need one thread
+ .forks(1) // we only need one fork
+ .shouldFailOnError(true) // this is important to fail the
test if the benchmark fails
+ .jvmArgs("-Xmx1G")
+ .timeout(TimeValue.seconds(6)) // 6 seconds should be enough,
even on slow machines
+ .build();
+ var results = new Runner(opt).run();
+ Assert.assertNotNull(results);
+ }
+}