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);
+    }
+}

Reply via email to