diff --git a/AGENTS.md b/AGENTS.md index e5e3a90f3c4..c0961300c37 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -593,16 +593,19 @@ Do **not** modify existing headers’ years. * `mvn -o verify` (long; only when appropriate) * Slow tests (entire repo): - * `mvn -o verify -PslowTestsOnly,-skipSlowTests,-formatting -Dmaven.javadoc.skip -Djapicmp.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500` + * `mvn -o verify -PslowTestsOnly,-skipSlowTests | tail -500` * Slow tests (by module): - * `mvn -o -pl verify -PslowTestsOnly,-skipSlowTests,-formatting -Dmaven.javadoc.skip -Djapicmp.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500` + * `mvn -o -pl verify -PslowTestsOnly,-skipSlowTests | tail -500` +* Slow tests (specific test): + + * `mvn -o -pl core/sail/shacl -PslowTestsOnly,-skipSlowTests -Dtest=ClassName#method verify | tail -500` * Integration tests (entire repo): - * `mvn -o verify -PskipUnitTests,-formatting -Dmaven.javadoc.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500` + * `mvn -o verify -PskipUnitTests | tail -500` * Integration tests (by module): - * `mvn -o -pl verify -PskipUnitTests,-formatting -Dmaven.javadoc.skip -Denforcer.skip -Danimal.sniffer.skip | tail -500` + * `mvn -o -pl verify -PskipUnitTests | tail -500` * Useful flags: * `-Dtest=ClassName` diff --git a/assembly-descriptors/pom.xml b/assembly-descriptors/pom.xml index 5c4721a849f..d7a5ac3d08e 100644 --- a/assembly-descriptors/pom.xml +++ b/assembly-descriptors/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-assembly-descriptors RDF4J: Assembly Descriptors diff --git a/assembly/pom.xml b/assembly/pom.xml index c4ed09b9904..11e32ba07b0 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-assembly pom diff --git a/bom/pom.xml b/bom/pom.xml index 3d804a0381d..6c556f153ac 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-bom pom diff --git a/compliance/elasticsearch/pom.xml b/compliance/elasticsearch/pom.xml index e5074472063..86f1d262125 100644 --- a/compliance/elasticsearch/pom.xml +++ b/compliance/elasticsearch/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-compliance - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-elasticsearch-compliance RDF4J: Elasticsearch Sail Tests diff --git a/compliance/geosparql/pom.xml b/compliance/geosparql/pom.xml index 81aa33c74f8..9062c9a3b78 100644 --- a/compliance/geosparql/pom.xml +++ b/compliance/geosparql/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-compliance - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-geosparql-compliance RDF4J: GeoSPARQL compliance tests diff --git a/compliance/lucene/pom.xml b/compliance/lucene/pom.xml index 0032b2d0c5b..dd3c41c533d 100644 --- a/compliance/lucene/pom.xml +++ b/compliance/lucene/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-compliance - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-lucene-compliance RDF4J: Lucene Sail Tests diff --git a/compliance/model/pom.xml b/compliance/model/pom.xml index 75e9cae5557..2e661418bd1 100644 --- a/compliance/model/pom.xml +++ b/compliance/model/pom.xml @@ -3,7 +3,7 @@ rdf4j-compliance org.eclipse.rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT 4.0.0 rdf4j-model-compliance diff --git a/compliance/pom.xml b/compliance/pom.xml index d43b32c6b2c..848a28b27c0 100644 --- a/compliance/pom.xml +++ b/compliance/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-compliance pom diff --git a/compliance/repository/pom.xml b/compliance/repository/pom.xml index 0b93ae5f1b2..7c83cfc3506 100644 --- a/compliance/repository/pom.xml +++ b/compliance/repository/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-compliance - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-compliance war diff --git a/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/SPARQLStoreConnectionTest.java b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/SPARQLStoreConnectionTest.java index d23d25184f7..9d5b452baaa 100644 --- a/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/SPARQLStoreConnectionTest.java +++ b/compliance/repository/src/test/java/org/eclipse/rdf4j/repository/sparql/SPARQLStoreConnectionTest.java @@ -524,4 +524,12 @@ public void testRemoveStatementsFromContextSingleTransaction(IsolationLevel leve public void testClearStatementsFromContextSingleTransaction(IsolationLevel level) throws Exception { super.testClearStatementsFromContextSingleTransaction(level); } + + @ParameterizedTest + @MethodSource("parameters") + @Disabled("relies on pending updates being visible in own connection") + @Override + public void testRollbackAfterInterrupt(IsolationLevel level) { + super.testRollbackAfterInterrupt(level); + } } diff --git a/compliance/rio/pom.xml b/compliance/rio/pom.xml index a5edac65d27..e1ad5da306f 100644 --- a/compliance/rio/pom.xml +++ b/compliance/rio/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-compliance - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-compliance RDF4J: Rio compliance tests diff --git a/compliance/solr/pom.xml b/compliance/solr/pom.xml index 9aa16eabdde..9133a827442 100644 --- a/compliance/solr/pom.xml +++ b/compliance/solr/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-compliance - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-solr-compliance RDF4J: Solr Sail Tests diff --git a/compliance/sparql/pom.xml b/compliance/sparql/pom.xml index 7df196bbbe4..0d6cb796c46 100644 --- a/compliance/sparql/pom.xml +++ b/compliance/sparql/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-compliance - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sparql-compliance war diff --git a/core/client/pom.xml b/core/client/pom.xml index d5eda75d8c4..e896238dc4b 100644 --- a/core/client/pom.xml +++ b/core/client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-client RDF4J: Client Libraries diff --git a/core/collection-factory/api/pom.xml b/core/collection-factory/api/pom.xml index 42229ebe467..515cad668a7 100644 --- a/core/collection-factory/api/pom.xml +++ b/core/collection-factory/api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-collection-factory - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-collection-factory-api RDF4J: Collection Factory - API diff --git a/core/collection-factory/mapdb/pom.xml b/core/collection-factory/mapdb/pom.xml index 02f8c48aa20..647c7ab875a 100644 --- a/core/collection-factory/mapdb/pom.xml +++ b/core/collection-factory/mapdb/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-collection-factory - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-collection-factory-mapdb RDF4J: Collection Factory - Map DB backed diff --git a/core/collection-factory/mapdb3/pom.xml b/core/collection-factory/mapdb3/pom.xml index bfca3b2dd13..af688ea3a65 100644 --- a/core/collection-factory/mapdb3/pom.xml +++ b/core/collection-factory/mapdb3/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-collection-factory - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-collection-factory-mapdb3 RDF4J: Collection Factory - Map DB v3 backed diff --git a/core/collection-factory/pom.xml b/core/collection-factory/pom.xml index b315c27ed62..ba294882025 100644 --- a/core/collection-factory/pom.xml +++ b/core/collection-factory/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-collection-factory pom diff --git a/core/common/annotation/pom.xml b/core/common/annotation/pom.xml index af81e6b443a..bcfa5aea40a 100644 --- a/core/common/annotation/pom.xml +++ b/core/common/annotation/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-annotation RDF4J: common annotation diff --git a/core/common/exception/pom.xml b/core/common/exception/pom.xml index 8ec5ac952f6..3cd7a477bda 100644 --- a/core/common/exception/pom.xml +++ b/core/common/exception/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-exception RDF4J: common exception diff --git a/core/common/io/pom.xml b/core/common/io/pom.xml index a4dfca5dc1a..50b8dbc3c8b 100644 --- a/core/common/io/pom.xml +++ b/core/common/io/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-io RDF4J: common IO diff --git a/core/common/iterator/pom.xml b/core/common/iterator/pom.xml index dd332d41149..e7d415785f1 100644 --- a/core/common/iterator/pom.xml +++ b/core/common/iterator/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-iterator RDF4J: common iterators diff --git a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/FilterIteration.java b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/FilterIteration.java index 04b88933b5f..f2832542ab7 100644 --- a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/FilterIteration.java +++ b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/FilterIteration.java @@ -102,10 +102,6 @@ private void findNextElement() { } } - // We know that nextElement has to be null and that wrappedIter.hasNext() must be true, based on the code - // above. To be sure that these invariants don't change we also assert them below. - assert nextElement == null && wrappedIter.hasNext(); - do { E result; if (Thread.currentThread().isInterrupted()) { @@ -113,6 +109,11 @@ private void findNextElement() { return; } try { + // We know that nextElement has to be null and that wrappedIter.hasNext() must be true, based on the + // code + // above. To be sure that these invariants don't change we also assert them below. + assert nextElement == null && wrappedIter.hasNext(); + result = wrappedIter.next(); } catch (NoSuchElementException e) { close(); diff --git a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/LookAheadIteration.java b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/LookAheadIteration.java index 7e7b114f4bf..3ee64bc55c6 100644 --- a/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/LookAheadIteration.java +++ b/core/common/iterator/src/main/java/org/eclipse/rdf4j/common/iteration/LookAheadIteration.java @@ -13,12 +13,16 @@ import java.util.NoSuchElementException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * An Iteration that looks one element ahead, if necessary, to handle calls to {@link #hasNext}. This is a convenient * super class for Iterations that have no easy way to tell if there are any more results, but still should implement * the java.util.Iteration interface. */ public abstract class LookAheadIteration extends AbstractCloseableIteration { + private static final Logger log = LoggerFactory.getLogger(LookAheadIteration.class); /*-----------* * Variables * @@ -50,7 +54,14 @@ public final boolean hasNext() { return false; } - return lookAhead() != null; + try { + return lookAhead() != null; + } catch (NoSuchElementException logged) { + // The lookAhead() method shouldn't throw a NoSuchElementException since it should return null when there + // are no more elements. + log.trace("LookAheadIteration threw NoSuchElementException:", logged); + return false; + } } @Override diff --git a/core/common/order/pom.xml b/core/common/order/pom.xml index 79ec18f60e4..9fdbb4dac05 100644 --- a/core/common/order/pom.xml +++ b/core/common/order/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-order RDF4J: common order diff --git a/core/common/pom.xml b/core/common/pom.xml index f60c85d3202..7c2d8995267 100644 --- a/core/common/pom.xml +++ b/core/common/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common pom diff --git a/core/common/text/pom.xml b/core/common/text/pom.xml index 9a674d41662..6787dc2930f 100644 --- a/core/common/text/pom.xml +++ b/core/common/text/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-text RDF4J: common text diff --git a/core/common/transaction/pom.xml b/core/common/transaction/pom.xml index 3ccdb318cdd..997f5e44f96 100644 --- a/core/common/transaction/pom.xml +++ b/core/common/transaction/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-transaction RDF4J: common transaction diff --git a/core/common/xml/pom.xml b/core/common/xml/pom.xml index cd08ac1ff58..0b25c265142 100644 --- a/core/common/xml/pom.xml +++ b/core/common/xml/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-common - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-common-xml RDF4J: common XML diff --git a/core/http/client/pom.xml b/core/http/client/pom.xml index 6aa0f9091c1..14eb63b82d9 100644 --- a/core/http/client/pom.xml +++ b/core/http/client/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-http - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-http-client RDF4J: HTTP client diff --git a/core/http/pom.xml b/core/http/pom.xml index 30c11f8d0c4..2994077b6f1 100644 --- a/core/http/pom.xml +++ b/core/http/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-http pom diff --git a/core/http/protocol/pom.xml b/core/http/protocol/pom.xml index 82f56970e66..c42876debca 100644 --- a/core/http/protocol/pom.xml +++ b/core/http/protocol/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-http - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-http-protocol RDF4J: HTTP protocol diff --git a/core/model-api/pom.xml b/core/model-api/pom.xml index 285953f878e..bb84bc98273 100644 --- a/core/model-api/pom.xml +++ b/core/model-api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-model-api RDF4J: Model API diff --git a/core/model-vocabulary/pom.xml b/core/model-vocabulary/pom.xml index 292b8bff7e1..efd41a48f6c 100644 --- a/core/model-vocabulary/pom.xml +++ b/core/model-vocabulary/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-model-vocabulary RDF4J: RDF Vocabularies diff --git a/core/model/pom.xml b/core/model/pom.xml index 3223807fa4f..74cca9bb0c3 100644 --- a/core/model/pom.xml +++ b/core/model/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-model RDF4J: Model diff --git a/core/pom.xml b/core/pom.xml index 242470997f1..262143c849c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-core pom diff --git a/core/query/pom.xml b/core/query/pom.xml index 09f0f018e99..a82e0d8f8b5 100644 --- a/core/query/pom.xml +++ b/core/query/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-query RDF4J: Query diff --git a/core/queryalgebra/evaluation/pom.xml b/core/queryalgebra/evaluation/pom.xml index 2822725c964..535581f475f 100644 --- a/core/queryalgebra/evaluation/pom.xml +++ b/core/queryalgebra/evaluation/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryalgebra - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryalgebra-evaluation RDF4J: Query algebra - evaluation diff --git a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java index 2468897ab5e..901557a1b78 100644 --- a/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java +++ b/core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/DefaultEvaluationStrategy.java @@ -217,7 +217,7 @@ protected static CloseableIteration evaluate(TupleFunction func, throws QueryEvaluationException { final CloseableIteration> iter = func .evaluate(valueFactory, argValues); - return new LookAheadIteration() { + return new LookAheadIteration<>() { @Override public BindingSet getNextElement() throws QueryEvaluationException { @@ -712,7 +712,7 @@ protected QueryEvaluationStep prepare(Distinct node, QueryEvaluationContext cont final CollectionFactory cf = this.getCollectionFactory().get(); return bindings -> { final CloseableIteration evaluate = child.evaluate(bindings); - return new DistinctIteration(evaluate, cf.createSetOfBindingSets()) { + return new DistinctIteration<>(evaluate, cf.createSetOfBindingSets()) { @Override protected void handleClose() throws QueryEvaluationException { diff --git a/core/queryalgebra/geosparql/pom.xml b/core/queryalgebra/geosparql/pom.xml index 38f3d8817a3..57b5553cce1 100644 --- a/core/queryalgebra/geosparql/pom.xml +++ b/core/queryalgebra/geosparql/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryalgebra - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryalgebra-geosparql RDF4J: Query algebra - GeoSPARQL diff --git a/core/queryalgebra/model/pom.xml b/core/queryalgebra/model/pom.xml index e8f711f02e7..70cc2d0b945 100644 --- a/core/queryalgebra/model/pom.xml +++ b/core/queryalgebra/model/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryalgebra - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryalgebra-model RDF4J: Query algebra - model diff --git a/core/queryalgebra/pom.xml b/core/queryalgebra/pom.xml index 90b02c52d98..a873d735fd7 100644 --- a/core/queryalgebra/pom.xml +++ b/core/queryalgebra/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryalgebra pom diff --git a/core/queryparser/api/pom.xml b/core/queryparser/api/pom.xml index 4dc73b9fc46..e9038834d3f 100644 --- a/core/queryparser/api/pom.xml +++ b/core/queryparser/api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryparser - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryparser-api RDF4J: Query parser - API diff --git a/core/queryparser/pom.xml b/core/queryparser/pom.xml index 94240c87a83..b7371348ece 100644 --- a/core/queryparser/pom.xml +++ b/core/queryparser/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryparser pom diff --git a/core/queryparser/sparql/pom.xml b/core/queryparser/sparql/pom.xml index 0d1ae0bf29e..7adf023b40e 100644 --- a/core/queryparser/sparql/pom.xml +++ b/core/queryparser/sparql/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryparser - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryparser-sparql RDF4J: Query parser - SPARQL diff --git a/core/queryrender/pom.xml b/core/queryrender/pom.xml index ea7ae6fb2b3..a765bfdeb90 100644 --- a/core/queryrender/pom.xml +++ b/core/queryrender/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryrender RDF4J: Query Rendering diff --git a/core/queryresultio/api/pom.xml b/core/queryresultio/api/pom.xml index 7b1c7c00533..cd997e7eea0 100644 --- a/core/queryresultio/api/pom.xml +++ b/core/queryresultio/api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryresultio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryresultio-api RDF4J: Query result IO - API diff --git a/core/queryresultio/binary/pom.xml b/core/queryresultio/binary/pom.xml index ec04ccc05f9..05777c3e2da 100644 --- a/core/queryresultio/binary/pom.xml +++ b/core/queryresultio/binary/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryresultio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryresultio-binary RDF4J: Query result IO - binary diff --git a/core/queryresultio/pom.xml b/core/queryresultio/pom.xml index 826fee3779e..452df71f2e0 100644 --- a/core/queryresultio/pom.xml +++ b/core/queryresultio/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryresultio pom diff --git a/core/queryresultio/sparqljson/pom.xml b/core/queryresultio/sparqljson/pom.xml index 9918f32af65..d2ee8b37011 100644 --- a/core/queryresultio/sparqljson/pom.xml +++ b/core/queryresultio/sparqljson/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryresultio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryresultio-sparqljson RDF4J: Query result IO - SPARQL/JSON diff --git a/core/queryresultio/sparqlxml/pom.xml b/core/queryresultio/sparqlxml/pom.xml index f5c5cc20fd7..3b033592eb8 100644 --- a/core/queryresultio/sparqlxml/pom.xml +++ b/core/queryresultio/sparqlxml/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryresultio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryresultio-sparqlxml RDF4J: Query result IO - SPARQL/XML diff --git a/core/queryresultio/text/pom.xml b/core/queryresultio/text/pom.xml index 3ca2b8a1557..5415c638425 100644 --- a/core/queryresultio/text/pom.xml +++ b/core/queryresultio/text/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-queryresultio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryresultio-text RDF4J: Query result IO - plain text booleans diff --git a/core/queryresultio/text/src/main/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLResultsTSVWriter.java b/core/queryresultio/text/src/main/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLResultsTSVWriter.java index 3bcbfc74a1a..9769a75766a 100644 --- a/core/queryresultio/text/src/main/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLResultsTSVWriter.java +++ b/core/queryresultio/text/src/main/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLResultsTSVWriter.java @@ -194,21 +194,15 @@ private void writeLiteral(Literal lit) throws IOException { // Append the literal's language writer.write("@"); writer.write(lit.getLanguage().get()); - } else if (!XSD.STRING.equals(datatype) || !xsdStringToPlainLiteral()) { - writer.write("\""); - writer.write(encoded); - writer.write("\""); - // Append the literal's datatype - writer.write("^^"); - writeURI(datatype); - } else if (!label.isEmpty() && encoded.equals(label) && label.charAt(0) != '<' && label.charAt(0) != '_' - && !label.matches("^[\\+\\-]?[\\d\\.].*")) { - // no need to include double quotes - writer.write(encoded); } else { writer.write("\""); writer.write(encoded); writer.write("\""); + // Append the literal's datatype if it's not xsd:string or if xsdStringToPlainLiteral is false + if (!XSD.STRING.equals(datatype) || !xsdStringToPlainLiteral()) { + writer.write("^^"); + writeURI(datatype); + } } } diff --git a/core/queryresultio/text/src/test/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLTSVCustomTest.java b/core/queryresultio/text/src/test/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLTSVCustomTest.java index 2dcfc8d3131..a79915dc81d 100644 --- a/core/queryresultio/text/src/test/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLTSVCustomTest.java +++ b/core/queryresultio/text/src/test/java/org/eclipse/rdf4j/query/resultio/text/tsv/SPARQLTSVCustomTest.java @@ -69,6 +69,26 @@ public void testSES2126QuotedLiteralIntegerAsStringImplicitType() throws Excepti assertEquals("?test\n\"1\"\n", result); } + @Test + public void testQuotedXSDStringLiteral() throws Exception { + List bindingNames = List.of("test"); + TupleQueryResult tqr = new IteratingTupleQueryResult(bindingNames, + List.of(new ListBindingSet(bindingNames, + SimpleValueFactory.getInstance().createLiteral("example", XSD.STRING)))); + String result = writeTupleResult(tqr); + assertEquals("?test\n\"example\"\n", result); + } + + @Test + public void testQuotedXSDStringLiteralWithSpecialCharacters() throws Exception { + List bindingNames = List.of("test"); + TupleQueryResult tqr = new IteratingTupleQueryResult(bindingNames, + List.of(new ListBindingSet(bindingNames, SimpleValueFactory.getInstance() + .createLiteral("example\twith\nspecial\"characters", XSD.STRING)))); + String result = writeTupleResult(tqr); + assertEquals("?test\n\"example\\twith\\nspecial\\\"characters\"\n", result); + } + private String writeTupleResult(TupleQueryResult tqr) throws IOException, TupleQueryResultHandlerException, QueryEvaluationException { ByteArrayOutputStream output = new ByteArrayOutputStream(); diff --git a/core/repository/api/pom.xml b/core/repository/api/pom.xml index 85243b3d6bb..5263b5485fc 100644 --- a/core/repository/api/pom.xml +++ b/core/repository/api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-api RDF4J: Repository - API diff --git a/core/repository/contextaware/pom.xml b/core/repository/contextaware/pom.xml index 5692938bacf..d88f99b4ca9 100644 --- a/core/repository/contextaware/pom.xml +++ b/core/repository/contextaware/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-contextaware RDF4J: Repository - context aware (wrapper) diff --git a/core/repository/dataset/pom.xml b/core/repository/dataset/pom.xml index ca8654fa08a..03805296331 100644 --- a/core/repository/dataset/pom.xml +++ b/core/repository/dataset/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-dataset RDF4J: DatasetRepository (wrapper) diff --git a/core/repository/event/pom.xml b/core/repository/event/pom.xml index f22e17e4f3c..b33b51bca3c 100644 --- a/core/repository/event/pom.xml +++ b/core/repository/event/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-event RDF4J: Repository - event (wrapper) diff --git a/core/repository/http/pom.xml b/core/repository/http/pom.xml index 94e7aa82f76..8098449942f 100644 --- a/core/repository/http/pom.xml +++ b/core/repository/http/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-http RDF4J: HTTPRepository diff --git a/core/repository/manager/pom.xml b/core/repository/manager/pom.xml index 7265653cccd..84acb9a8545 100644 --- a/core/repository/manager/pom.xml +++ b/core/repository/manager/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-manager RDF4J: Repository manager diff --git a/core/repository/pom.xml b/core/repository/pom.xml index f1d6b6d9a93..203db3590d5 100644 --- a/core/repository/pom.xml +++ b/core/repository/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository pom diff --git a/core/repository/sail/pom.xml b/core/repository/sail/pom.xml index 82e4d168491..df84b7c318e 100644 --- a/core/repository/sail/pom.xml +++ b/core/repository/sail/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-sail RDF4J: SailRepository diff --git a/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/SailRepositoryConnection.java b/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/SailRepositoryConnection.java index 350caee5270..b14e54c1676 100644 --- a/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/SailRepositoryConnection.java +++ b/core/repository/sail/src/main/java/org/eclipse/rdf4j/repository/sail/SailRepositoryConnection.java @@ -231,10 +231,17 @@ public void commit() throws RepositoryException { @Override public void rollback() throws RepositoryException { + // We still want to attempt to rollback the transaction even if the thread is interrupted. We restore the + // interrupted status when we are done. + boolean interrupted = Thread.interrupted(); try { sailConnection.rollback(); } catch (SailException e) { throw new RepositoryException(e); + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } } } diff --git a/core/repository/sparql/pom.xml b/core/repository/sparql/pom.xml index aacf5862c47..6ec77048e76 100644 --- a/core/repository/sparql/pom.xml +++ b/core/repository/sparql/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-repository - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-sparql RDF4J: SPARQL Repository diff --git a/core/rio/api/pom.xml b/core/rio/api/pom.xml index b40a28c4ffd..699962c59e8 100644 --- a/core/rio/api/pom.xml +++ b/core/rio/api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-api RDF4J: Rio - API diff --git a/core/rio/binary/pom.xml b/core/rio/binary/pom.xml index db4f0c92948..2fbeaf163b0 100644 --- a/core/rio/binary/pom.xml +++ b/core/rio/binary/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-binary RDF4J: Rio - Binary diff --git a/core/rio/datatypes/pom.xml b/core/rio/datatypes/pom.xml index 8e35af7d325..75453263294 100644 --- a/core/rio/datatypes/pom.xml +++ b/core/rio/datatypes/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-datatypes RDF4J: Rio - Datatypes diff --git a/core/rio/hdt/pom.xml b/core/rio/hdt/pom.xml index d728f5585a8..ecc1fe4e84d 100644 --- a/core/rio/hdt/pom.xml +++ b/core/rio/hdt/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-hdt jar diff --git a/core/rio/jsonld-legacy/pom.xml b/core/rio/jsonld-legacy/pom.xml index 5acbb2e4e60..e4e24776a49 100644 --- a/core/rio/jsonld-legacy/pom.xml +++ b/core/rio/jsonld-legacy/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-jsonld-legacy RDF4J: Rio - JSON-LD 1.0 (legacy) diff --git a/core/rio/jsonld/pom.xml b/core/rio/jsonld/pom.xml index a74f9d46900..fc9a36629e6 100644 --- a/core/rio/jsonld/pom.xml +++ b/core/rio/jsonld/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-jsonld RDF4J: Rio - JSON-LD diff --git a/core/rio/languages/pom.xml b/core/rio/languages/pom.xml index f49c5f20124..86c0a2365f4 100644 --- a/core/rio/languages/pom.xml +++ b/core/rio/languages/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-languages RDF4J: Rio - Languages diff --git a/core/rio/n3/pom.xml b/core/rio/n3/pom.xml index bd0a0bc3787..d2a38c44896 100644 --- a/core/rio/n3/pom.xml +++ b/core/rio/n3/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-n3 RDF4J: Rio - N3 (writer-only) diff --git a/core/rio/nquads/pom.xml b/core/rio/nquads/pom.xml index 615024c8e28..5082d6bac1b 100644 --- a/core/rio/nquads/pom.xml +++ b/core/rio/nquads/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-nquads RDF4J: Rio - N-Quads diff --git a/core/rio/ntriples/pom.xml b/core/rio/ntriples/pom.xml index 9cfb6c3c239..fddd42e5463 100644 --- a/core/rio/ntriples/pom.xml +++ b/core/rio/ntriples/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-ntriples RDF4J: Rio - N-Triples diff --git a/core/rio/pom.xml b/core/rio/pom.xml index ab2b94605a4..c2d0ce909cf 100644 --- a/core/rio/pom.xml +++ b/core/rio/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio pom diff --git a/core/rio/rdfjson/pom.xml b/core/rio/rdfjson/pom.xml index 8e09c92bc39..c8800e3d20f 100644 --- a/core/rio/rdfjson/pom.xml +++ b/core/rio/rdfjson/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-rdfjson RDF4J: Rio - RDF/JSON diff --git a/core/rio/rdfxml/pom.xml b/core/rio/rdfxml/pom.xml index 5d9af51a8dd..d8ce5a3ca9c 100644 --- a/core/rio/rdfxml/pom.xml +++ b/core/rio/rdfxml/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-rdfxml RDF4J: Rio - RDF/XML diff --git a/core/rio/trig/pom.xml b/core/rio/trig/pom.xml index 32d98e3cd04..529637be92c 100644 --- a/core/rio/trig/pom.xml +++ b/core/rio/trig/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-trig RDF4J: Rio - TriG diff --git a/core/rio/trix/pom.xml b/core/rio/trix/pom.xml index 3b2e0b33245..6de5577c41a 100644 --- a/core/rio/trix/pom.xml +++ b/core/rio/trix/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-trix RDF4J: Rio - TriX diff --git a/core/rio/turtle/pom.xml b/core/rio/turtle/pom.xml index 01fe4ee0e1c..39fe9c9db21 100644 --- a/core/rio/turtle/pom.xml +++ b/core/rio/turtle/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-rio - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-turtle RDF4J: Rio - Turtle diff --git a/core/sail/api/pom.xml b/core/sail/api/pom.xml index 04f32ad4f28..be83b7d755e 100644 --- a/core/sail/api/pom.xml +++ b/core/sail/api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-api RDF4J: Sail API diff --git a/core/sail/api/src/main/java/org/eclipse/rdf4j/common/concurrent/locks/ExclusiveReentrantLockManager.java b/core/sail/api/src/main/java/org/eclipse/rdf4j/common/concurrent/locks/ExclusiveReentrantLockManager.java index 6ac7fa4df12..e3139e18bac 100644 --- a/core/sail/api/src/main/java/org/eclipse/rdf4j/common/concurrent/locks/ExclusiveReentrantLockManager.java +++ b/core/sail/api/src/main/java/org/eclipse/rdf4j/common/concurrent/locks/ExclusiveReentrantLockManager.java @@ -45,6 +45,14 @@ public ExclusiveReentrantLockManager(boolean trackLocks) { } public ExclusiveReentrantLockManager(boolean trackLocks, int collectionFrequency) { + this(trackLocks, collectionFrequency, "ExclusiveReentrantLockManager"); + } + + public ExclusiveReentrantLockManager(String lockName) { + this(false, LockMonitoring.INITIAL_WAIT_TO_COLLECT, lockName); + } + + public ExclusiveReentrantLockManager(boolean trackLocks, int collectionFrequency, String lockName) { this.waitToCollect = collectionFrequency; @@ -52,7 +60,7 @@ public ExclusiveReentrantLockManager(boolean trackLocks, int collectionFrequency lockMonitoring = new LockTracking( true, - "ExclusiveReentrantLockManager", + lockName, LoggerFactory.getLogger(this.getClass()), waitToCollect, Lock.ExtendedSupplier.wrap(this::getExclusiveLockInner, this::tryExclusiveLockInner) @@ -61,7 +69,7 @@ public ExclusiveReentrantLockManager(boolean trackLocks, int collectionFrequency } else { lockMonitoring = new LockCleaner( false, - "ExclusiveReentrantLockManager", + lockName, LoggerFactory.getLogger(this.getClass()), Lock.ExtendedSupplier.wrap(this::getExclusiveLockInner, this::tryExclusiveLockInner) ); @@ -101,6 +109,16 @@ private Lock getExclusiveLockInner() throws InterruptedException { return lock; } else { lockMonitoring.runCleanup(); + lock = tryExclusiveLockInner(); + if (lock != null) { + return lock; + } + + Thread thread = owner.get(); + if (thread != null && !thread.isAlive()) { + logger.debug("Lock is held by dead thread: " + thread); + } + owner.wait(waitToCollect); } } while (true); diff --git a/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/InterruptedSailException.java b/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/InterruptedSailException.java new file mode 100644 index 00000000000..60eb00737e7 --- /dev/null +++ b/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/InterruptedSailException.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + ******************************************************************************/ + +package org.eclipse.rdf4j.sail; + +public class InterruptedSailException extends SailException { + + public InterruptedSailException(String s) { + super(s); + assert Thread.currentThread().isInterrupted(); + } + + public InterruptedSailException() { + super(); + assert Thread.currentThread().isInterrupted(); + } + + public InterruptedSailException(Throwable t) { + super(t); + assert Thread.currentThread().isInterrupted(); + } + + public InterruptedSailException(String msg, Throwable t) { + super(msg, t); + assert Thread.currentThread().isInterrupted(); + } +} diff --git a/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSail.java b/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSail.java index 55e58fa3fab..d44d8a3b3fa 100644 --- a/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSail.java +++ b/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSail.java @@ -40,7 +40,7 @@ public abstract class AbstractSail implements Sail { /** - * Default connection timeout on shutdown: 20,000 milliseconds. + * Default connection timeout on shutdown: 20,000 milliseconds (20 seconds). */ protected final static long DEFAULT_CONNECTION_TIMEOUT = 20000L; @@ -227,17 +227,19 @@ public void shutDown() throws SailException { try { SailConnection con = entry.getKey(); - if (con instanceof AbstractSailConnection) { - AbstractSailConnection sailCon = (AbstractSailConnection) con; - Thread owner = sailCon.getOwner(); - if (owner != Thread.currentThread()) { - owner.interrupt(); - // wait up to 1 second for the owner thread to die - owner.join(1000); - if (owner.isAlive()) { - logger.error( - "Closing active connection due to shut down and interrupted the owning thread of the connection {} but thread is still alive after 1000 ms!", - owner); + if (con.isOpen()) { + if (con instanceof AbstractSailConnection) { + AbstractSailConnection sailCon = (AbstractSailConnection) con; + Thread owner = sailCon.getOwner(); + if (owner != Thread.currentThread()) { + owner.interrupt(); + // wait up to 1 second for the owner thread to die + owner.join(1000); + if (owner.isAlive() && con.isOpen()) { + logger.error( + "Closing active connection due to shut down and interrupted the owning thread of the connection {} but thread is still alive after 1000 ms!", + owner); + } } } } @@ -257,6 +259,9 @@ public void shutDown() throws SailException { // Forcefully close any connections that are still open for (Map.Entry entry : activeConnectionsCopy.entrySet()) { SailConnection con = entry.getKey(); + if (!con.isOpen()) { + continue; + } Throwable stackTrace = entry.getValue(); if (stackTrace == null) { diff --git a/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSailConnection.java b/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSailConnection.java index ca9bb7b60fb..f964487e3c3 100644 --- a/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSailConnection.java +++ b/core/sail/api/src/main/java/org/eclipse/rdf4j/sail/helpers/AbstractSailConnection.java @@ -22,9 +22,9 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.LongAdder; import java.util.concurrent.locks.LockSupport; -import java.util.concurrent.locks.ReentrantLock; import org.eclipse.rdf4j.common.annotation.InternalUseOnly; +import org.eclipse.rdf4j.common.concurrent.locks.ExclusiveReentrantLockManager; import org.eclipse.rdf4j.common.concurrent.locks.Lock; import org.eclipse.rdf4j.common.concurrent.locks.diagnostics.ConcurrentCleaner; import org.eclipse.rdf4j.common.iteration.CloseableIteration; @@ -42,6 +42,7 @@ import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.Dataset; import org.eclipse.rdf4j.query.algebra.TupleExpr; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.UnknownSailTransactionStateException; @@ -101,14 +102,14 @@ public abstract class AbstractSailConnection implements SailConnection { private boolean isOpen = true; private static final VarHandle IS_OPEN; - private Thread owner; + private final Thread owner; /** * Lock used to prevent concurrent calls to update methods like addStatement, clear, commit, etc. within a * transaction. - * */ - private final ReentrantLock updateLock = new ReentrantLock(); + private final ExclusiveReentrantLockManager updateLock = new ExclusiveReentrantLockManager( + "AbstractSailConnection-updateLock"); private final LongAdder iterationsOpened = new LongAdder(); private final LongAdder iterationsClosed = new LongAdder(); @@ -201,7 +202,7 @@ public void begin(IsolationLevel isolationLevel) throws SailException { verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); try { if (isActive()) { throw new SailException("a transaction is already active on this connection."); @@ -210,8 +211,11 @@ public void begin(IsolationLevel isolationLevel) throws SailException { startTransactionInternal(); txnActive = true; } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -245,11 +249,13 @@ public final void close() throws SailException { if (!IS_OPEN.compareAndSet(this, true, false)) { return; } - try { - - waitForOtherOperations(true); + try { + sailBase.connectionClosed(this); + } finally { try { + waitForOtherOperations(true); + } finally { try { forceCloseActiveOperations(); } finally { @@ -272,11 +278,7 @@ public final void close() throws SailException { throw new SailException("Connection closed before all iterations were closed."); } } - - } finally { - sailBase.connectionClosed(this); } - } finally { } } @@ -505,15 +507,18 @@ public final void prepare() throws SailException { activeThread.setRelease(Thread.currentThread()); verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); try { if (txnActive) { prepareInternal(); txnPrepared = true; } } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -535,7 +540,7 @@ public final void commit() throws SailException { verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); try { if (txnActive) { if (!txnPrepared) { @@ -546,8 +551,11 @@ public final void commit() throws SailException { txnPrepared = false; } } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -571,12 +579,41 @@ public final void rollback() throws SailException { activeThread.setRelease(Thread.currentThread()); verifyIsOpen(); + Lock exclusiveLock = null; + InterruptedException exception = null; + try { + exclusiveLock = updateLock.getExclusiveLock(); - updateLock.lock(); + } catch (InterruptedException e) { + // we really want to finish rolling back, so we retry getting the lock even if we've been interrupted + logger.warn( + "Interrupted while trying to acquire exclusive lock to rollback transaction. Retrying one time.", + e); + exception = e; + try { + exclusiveLock = updateLock.getExclusiveLock(); + + } catch (InterruptedException e2) { + assert false + : "Interrupted a second time while trying to acquire exclusive lock to rollback transaction. This should never happen during testing"; + logger.error( + "Interrupted a second time while trying to acquire exclusive lock to rollback transaction. Giving up.", + e2); + Thread.currentThread().interrupt(); + throw new InterruptedSailException( + "Interrupted twice while trying to acquire exclusive lock to rollback transaction.", e); + } + } try { if (txnActive) { try { rollbackInternal(); + if (exception != null) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException( + "Interrupted while acquiring lock to allow for rollback. Retried lock and rolled back transaction successfully. Re-interrupted and re-threw exception.", + exception); + } } finally { txnActive = false; txnPrepared = false; @@ -586,7 +623,9 @@ public final void rollback() throws SailException { debugEnabled ? new Throwable() : null); } } finally { - updateLock.unlock(); + if (exclusiveLock != null) { + exclusiveLock.release(); + } } } finally { try { @@ -694,13 +733,16 @@ public final void endUpdate(UpdateContext op) throws SailException { verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); try { verifyIsActive(); endUpdateInternal(op); } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -750,14 +792,17 @@ public final void clear(Resource... contexts) throws SailException { verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); try { verifyIsActive(); clearInternal(contexts); statementsRemoved = true; } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -820,13 +865,17 @@ public final void setNamespace(String prefix, String name) throws SailException verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); + try { verifyIsActive(); setNamespaceInternal(prefix, name); } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -848,13 +897,17 @@ public final void removeNamespace(String prefix) throws SailException { verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); + try { verifyIsActive(); removeNamespaceInternal(prefix); } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -873,13 +926,17 @@ public final void clearNamespaces() throws SailException { verifyIsOpen(); - updateLock.lock(); + Lock exclusiveLock = updateLock.getExclusiveLock(); + try { verifyIsActive(); clearNamespacesInternal(); } finally { - updateLock.unlock(); + exclusiveLock.release(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(e); } finally { try { activeThread.setRelease(null); @@ -925,7 +982,8 @@ protected CloseableIteration registerIteration(Close if (debugEnabled) { var result = new SailBaseIteration<>(iter, this); - activeIterationsDebug.put(result, new Throwable("Unclosed iteration")); + activeIterationsDebug.put(result, + new Throwable("Unclosed iteration created in " + this.getClass().getName())); return result; } else { return new CleanerIteration<>(new SailBaseIteration<>(iter, this), cleaner); @@ -995,6 +1053,13 @@ protected boolean isActiveOperation() { return closed != opened; } + @InternalUseOnly + public boolean hasActiveIterations() { + long closed = iterationsClosed.sum(); + long opened = iterationsOpened.sum(); + return closed != opened; + } + protected AbstractSail getSailBase() { return sailBase; } @@ -1006,7 +1071,7 @@ private void forceCloseActiveOperations() throws SailException { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new SailException(e); + throw new InterruptedSailException(e); } } @@ -1023,7 +1088,7 @@ private void forceCloseActiveOperations() throws SailException { } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); - throw new SailException(e); + throw new InterruptedSailException(e); } logger.warn("Exception occurred while closing unclosed iterations.", e); } diff --git a/core/sail/base/pom.xml b/core/sail/base/pom.xml index 4ead34880f3..13a81530f9c 100644 --- a/core/sail/base/pom.xml +++ b/core/sail/base/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-base RDF4J: Sail base implementations diff --git a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java index f2d777759f8..95ed831dc6e 100644 --- a/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java +++ b/core/sail/base/src/main/java/org/eclipse/rdf4j/sail/base/SailSourceBranch.java @@ -207,22 +207,60 @@ public void close() throws SailException { */ private void removeThisFromPendingWithoutCausingDeadlock() { long tryLockMillis = 10; + boolean interrupted = false; while (pending.contains(this)) { boolean locked = false; try { - locked = semaphore.tryLock(tryLockMillis *= 2, TimeUnit.MILLISECONDS); + try { + locked = semaphore.tryLock(500, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + interrupted = true; + try { + // retry once if interrupted + locked = semaphore.tryLock(500, TimeUnit.MILLISECONDS); + if (locked) { + pending.remove(this); + break; + } else { + if (pending.contains(this)) { + throw new SailException( + "Interrupted while trying to remove Changeset from pending list, giving up.", + e); + } else { + // Changeset was removed from pending by another thread, so we can exit the loop + break; + } + + } + } catch (InterruptedException e1) { + if (pending.contains(this)) { + throw new SailException( + "Interrupted while trying to remove Changeset from pending list, giving up.", + e1); + } else { + // Changeset was removed from pending by another thread, so we can exit the loop + break; + } + } + } if (locked) { pending.remove(this); + break; } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new SailException(e); } finally { - if (locked) { - semaphore.unlock(); + try { + if (locked) { + semaphore.unlock(); + } + } finally { + if (interrupted) { + Thread.currentThread().interrupt(); + } } + } + tryLockMillis = Math.min(tryLockMillis * 2, 1000); } } diff --git a/core/sail/elasticsearch-store/pom.xml b/core/sail/elasticsearch-store/pom.xml index 0b1071d6ef3..16cfec25798 100644 --- a/core/sail/elasticsearch-store/pom.xml +++ b/core/sail/elasticsearch-store/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-elasticsearch-store RDF4J: Elasticsearch Store diff --git a/core/sail/elasticsearch/pom.xml b/core/sail/elasticsearch/pom.xml index 24f766b80fc..efaaeef072e 100644 --- a/core/sail/elasticsearch/pom.xml +++ b/core/sail/elasticsearch/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-elasticsearch RDF4J: Elastic Search Sail Index diff --git a/core/sail/extensible-store/pom.xml b/core/sail/extensible-store/pom.xml index af24c9c0702..ba19e28d29a 100644 --- a/core/sail/extensible-store/pom.xml +++ b/core/sail/extensible-store/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-extensible-store RDF4J: Extensible Store diff --git a/core/sail/extensible-store/src/main/java/org/eclipse/rdf4j/sail/extensiblestore/EagerReadCache.java b/core/sail/extensible-store/src/main/java/org/eclipse/rdf4j/sail/extensiblestore/EagerReadCache.java index cd393f77046..ea784fd9817 100644 --- a/core/sail/extensible-store/src/main/java/org/eclipse/rdf4j/sail/extensiblestore/EagerReadCache.java +++ b/core/sail/extensible-store/src/main/java/org/eclipse/rdf4j/sail/extensiblestore/EagerReadCache.java @@ -21,6 +21,7 @@ import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.impl.LinkedHashModel; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.extensiblestore.valuefactory.ExtensibleStatement; import org.slf4j.Logger; @@ -166,7 +167,7 @@ private boolean isLowOnMemory() { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new SailException(e); + throw new InterruptedSailException(e); } System.gc(); @@ -175,7 +176,7 @@ private boolean isLowOnMemory() { Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new SailException(e); + throw new InterruptedSailException(e); } return getFreeToAllocateMemory() < MIN_AVAILABLE_MEM; diff --git a/core/sail/inferencer/pom.xml b/core/sail/inferencer/pom.xml index 501efe70b3d..7040c569ace 100644 --- a/core/sail/inferencer/pom.xml +++ b/core/sail/inferencer/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-inferencer RDF4J: Inferencer Sails diff --git a/core/sail/lmdb/pom.xml b/core/sail/lmdb/pom.xml index 4641494df00..e529a1744ae 100644 --- a/core/sail/lmdb/pom.xml +++ b/core/sail/lmdb/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-lmdb RDF4J: LmdbStore diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java index 02e7d71bf5d..90212ad598b 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbSailStore.java @@ -41,6 +41,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.model.ValueFactory; import org.eclipse.rdf4j.query.algebra.evaluation.impl.EvaluationStatistics; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.base.BackingSailSource; import org.eclipse.rdf4j.sail.base.SailDataset; @@ -256,7 +257,7 @@ public void close() throws SailException { } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new SailException(e); + throw new InterruptedSailException(e); } } finally { tripleStore.close(); diff --git a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java index f4f41b58fb3..f13bde517ab 100644 --- a/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java +++ b/core/sail/lmdb/src/main/java/org/eclipse/rdf4j/sail/lmdb/LmdbStore.java @@ -36,6 +36,7 @@ import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; import org.eclipse.rdf4j.repository.sparql.federation.SPARQLServiceResolver; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.NotifyingSailConnection; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.base.SailSource; @@ -376,7 +377,7 @@ protected Lock getTransactionLock(IsolationLevel level) throws SailException { } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new SailException(e); + throw new InterruptedSailException(e); } finally { txnLockManager.unlock(); } diff --git a/core/sail/lucene-api/pom.xml b/core/sail/lucene-api/pom.xml index 145bea98d30..14cd1b73a1c 100644 --- a/core/sail/lucene-api/pom.xml +++ b/core/sail/lucene-api/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-lucene-api RDF4J: Lucene Sail API diff --git a/core/sail/lucene/pom.xml b/core/sail/lucene/pom.xml index 1b2b13f3bf7..1b64636e76f 100644 --- a/core/sail/lucene/pom.xml +++ b/core/sail/lucene/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-lucene RDF4J: Lucene Sail Index diff --git a/core/sail/memory/pom.xml b/core/sail/memory/pom.xml index 01851743cf5..b3828b16ea9 100644 --- a/core/sail/memory/pom.xml +++ b/core/sail/memory/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-memory RDF4J: MemoryStore diff --git a/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemorySailStore.java b/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemorySailStore.java index 47676926f39..6929321f807 100644 --- a/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemorySailStore.java +++ b/core/sail/memory/src/main/java/org/eclipse/rdf4j/sail/memory/MemorySailStore.java @@ -136,7 +136,8 @@ class MemorySailStore implements SailStore { /** * Lock manager used to prevent concurrent writes. */ - private final ExclusiveReentrantLockManager txnLockManager = new ExclusiveReentrantLockManager(); + private final ExclusiveReentrantLockManager txnLockManager = new ExclusiveReentrantLockManager( + "MemorySailStore-txnLockManager"); /** * Cleanup thread that removes deprecated statements when no other threads are accessing this list. Seee @@ -520,8 +521,7 @@ protected void scheduleSnapshotCleanup() { cleanSnapshots(); } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.info("snapshot cleanup interrupted"); + logger.debug("snapshot cleanup interrupted"); } }; diff --git a/core/sail/model/pom.xml b/core/sail/model/pom.xml index a010804afbb..7ef71dbdd90 100644 --- a/core/sail/model/pom.xml +++ b/core/sail/model/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-model RDF4J: Sail Model diff --git a/core/sail/nativerdf/pom.xml b/core/sail/nativerdf/pom.xml index 345fdefaab2..2b16a33bc33 100644 --- a/core/sail/nativerdf/pom.xml +++ b/core/sail/nativerdf/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-nativerdf RDF4J: NativeStore diff --git a/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java b/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java index e156083b8ef..992154b76ac 100644 --- a/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java +++ b/core/sail/nativerdf/src/main/java/org/eclipse/rdf4j/sail/nativerdf/NativeStore.java @@ -37,6 +37,7 @@ import org.eclipse.rdf4j.query.algebra.evaluation.federation.FederatedServiceResolverClient; import org.eclipse.rdf4j.query.algebra.evaluation.impl.StrictEvaluationStrategyFactory; import org.eclipse.rdf4j.repository.sparql.federation.SPARQLServiceResolver; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.NotifyingSailConnection; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.base.SailSource; @@ -468,7 +469,7 @@ protected Lock getTransactionLock(IsolationLevel level) throws SailException { } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new SailException(e); + throw new InterruptedSailException(e); } finally { txnLockManager.unlock(); } diff --git a/core/sail/pom.xml b/core/sail/pom.xml index 65f8cfa30c9..c5efd5a1231 100644 --- a/core/sail/pom.xml +++ b/core/sail/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail pom diff --git a/core/sail/shacl/pom.xml b/core/sail/shacl/pom.xml index 4be51f25b05..cea9f692e45 100644 --- a/core/sail/shacl/pom.xml +++ b/core/sail/shacl/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-shacl RDF4J: SHACL diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java index 81adf25db1d..b8ecfc694c5 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java @@ -50,6 +50,7 @@ import org.eclipse.rdf4j.sail.Sail; import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.helpers.AbstractSail; import org.eclipse.rdf4j.sail.memory.MemoryStore; import org.eclipse.rdf4j.sail.memory.MemoryStoreConnection; import org.eclipse.rdf4j.sail.shacl.ast.ContextWithShape; @@ -194,6 +195,7 @@ public class ShaclSail extends ShaclSailBaseConfiguration { private final AtomicBoolean initialized = new AtomicBoolean(false); private final RevivableExecutorService executorService; + private int connectionTimeOutMillis = -1; @InternalUseOnly StampedLockManager.Cache>.WritableState getCachedShapesForWriting() @@ -207,6 +209,19 @@ public StampedLockManager.Cache>.ReadableState getCachedS return cachedShapes.getReadState(); } + public boolean isShutdown() { + return !initialized.get(); + } + + @InternalUseOnly + public void setConnectionTimeOut(int connectionTimeOutMillis) { + NotifyingSail baseSail = getBaseSail(); + if (baseSail instanceof AbstractSail) { + ((AbstractSail) baseSail).setConnectionTimeOut(connectionTimeOutMillis); + } + this.connectionTimeOutMillis = connectionTimeOutMillis; + } + static class CleanableState implements Runnable { private final AtomicBoolean initialized; @@ -255,7 +270,7 @@ protected RevivableExecutorService getExecutorService() { () -> Executors.newFixedThreadPool(AVAILABLE_PROCESSORS, r -> { Thread t = Executors.defaultThreadFactory().newThread(r); - // this thread pool does not need to stick around if the all other threads are done, because + // This thread pool does not need to stick around if the all other threads are done, because // it is only used for SHACL validation and if all other threads have ended then there would // be no thread to receive the validation results. t.setDaemon(true); @@ -442,21 +457,27 @@ public List getShapes(RepositoryConnection shapesRepoConnectio @Override public synchronized void shutDown() throws SailException { - if (shapesRepo != null) { - shapesRepo.shutDown(); - shapesRepo = null; - } - - cachedShapes = null; + initialized.set(false); - boolean terminated = shutdownExecutorService(false); + try { + boolean terminated = shutdownExecutorService(false); - initialized.set(false); - super.shutDown(); + if (!terminated) { + shutdownExecutorService(true); + } + } finally { + try { + if (shapesRepo != null) { + shapesRepo.shutDown(); + shapesRepo = null; + } - if (!terminated) { - shutdownExecutorService(true); + cachedShapes = null; + } finally { + super.shutDown(); + } } + } private boolean shutdownExecutorService(boolean forced) { @@ -464,7 +485,7 @@ private boolean shutdownExecutorService(boolean forced) { executorService.shutdown(); try { - terminated = executorService.awaitTermination(200, TimeUnit.MILLISECONDS); + terminated = executorService.awaitTermination(1000, TimeUnit.MILLISECONDS); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } @@ -480,6 +501,13 @@ private boolean shutdownExecutorService(boolean forced) { } Future submitToExecutorService(Callable runnable) { + if (isShutdown()) { + throw new SailException("ShaclSail is shutdown."); + } + if (Thread.currentThread().isInterrupted()) { + throw new SailException("Interrupted while submitting to executor service."); + } + return executorService.submit(runnable); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java index 829fdd91766..c8c6db97dcb 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java @@ -11,18 +11,20 @@ package org.eclipse.rdf4j.sail.shacl; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; -import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.stream.Stream; import org.eclipse.rdf4j.common.concurrent.locks.Lock; @@ -106,6 +108,10 @@ public class ShaclSailConnection extends NotifyingSailConnectionWrapper implemen private TransactionSetting[] transactionSettingsRaw = new TransactionSetting[0]; private volatile boolean closed; + private volatile List> futures; + + private List shapeValidatorContainers; + ShaclSailConnection(ShaclSail sail, NotifyingSailConnection connection, SailConnection previousStateConnection, SailRepositoryConnection shapesRepoConnection, SailConnection serializableConnection) { super(connection); @@ -171,7 +177,11 @@ public void begin(IsolationLevel level) throws SailException { if (closed) { throw new SailException("Connection is closed"); } + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown"); + } + shapeValidatorContainers = new ArrayList<>(); currentIsolationLevel = level; assert addedStatements == null; @@ -346,40 +356,54 @@ public void rollback() throws SailException { } try { - if (readableShapesCache != null) { - readableShapesCache.close(); - readableShapesCache = null; - } - - if (writableShapesCache != null) { - writableShapesCache.purge(); - writableShapesCache.close(); - writableShapesCache = null; - } - - if (previousStateConnection != null && previousStateConnection.isActive()) { - previousStateConnection.rollback(); + try { + readableShapesCache.close(); + } finally { + readableShapesCache = null; + } } } finally { try { - if (shapesRepoConnection.isActive()) { - shapesRepoConnection.rollback(); + if (writableShapesCache != null) { + try { + writableShapesCache.purge(); + } finally { + try { + writableShapesCache.close(); + } finally { + writableShapesCache = null; + } + } } - } finally { - try { - if (isActive()) { - super.rollback(); + if (previousStateConnection != null && previousStateConnection.isActive()) { + previousStateConnection.rollback(); } - } finally { - cleanup(); + try { + if (serializableConnection != null && serializableConnection.isActive()) { + serializableConnection.rollback(); + } + } finally { + try { + if (shapesRepoConnection.isActive()) { + shapesRepoConnection.rollback(); + } + } finally { + try { + if (isActive()) { + super.rollback(); + } + } finally { + cleanup(); + } + } + } } } } - } private void cleanup() { @@ -489,6 +513,14 @@ void prepareValidation(ValidationSettings validationSettings) throws Interrupted rdfsSubClassOfReasoner = RdfsSubClassOfReasoner.createReasoner(this, validationSettings); } + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown"); + } + + if (Thread.interrupted()) { + throw new InterruptedException(); + } + if (!isBulkValidation()) { fillAddedAndRemovedStatementRepositories(); } @@ -529,37 +561,124 @@ private ValidationReport performValidation(List shapes, boolea connectionsGroup)) .filter(ShapeValidationContainer::hasPlanNode) + .peek(s -> { + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown"); + } + if (closed) { + throw new SailException("Connection is closed"); + } + synchronized (shapeValidatorContainers) { + try { + if (closed) { + try { + s.forceClose(); + } catch (Throwable ignored) { + logger.debug("Throwable was ignored while closing connection", ignored); + } + throw new SailException("Connection is closed"); + } + shapeValidatorContainers.add(s); + } catch (Throwable t) { + try { + s.forceClose(); + } catch (Throwable ignored) { + logger.debug("Throwable was ignored while closing connection", ignored); + } + if (closed) { + throw new SailException("Connection is closed", t); + } + throw t; + } + } + }) .map(validationContainer -> validationContainer::performValidation); List validationResultIterators = new ArrayList<>(numberOfShapes); - List> futures = Collections.emptyList(); + futures = new ArrayList>(numberOfShapes); boolean parallelValidation = numberOfShapes > 1 && isParallelValidation(); try { - futures = callableStream + callableStream .map(callable -> { if (Thread.currentThread().isInterrupted()) { return null; } + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown"); + } + if (closed) { + throw new SailException("Connection is closed"); + } + if (parallelValidation) { - return sail.submitToExecutorService(callable); + try { + return sail.submitToExecutorService(callable); + } catch (Throwable e) { + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown", e); + } + if (closed) { + throw new SailException("Connection is closed", e); + } + throw e; + } } else { FutureTask futureTask = new FutureTask<>(callable); futureTask.run(); return futureTask; } }) - .collect(Collectors.toList()); + .filter(Objects::nonNull) + .forEach(f -> { + synchronized (futures) { + try { + if (closed) { + f.cancel(true); + throw new SailException("Connection is closed"); + } + futures.add(f); + } catch (Throwable t) { + f.cancel(true); + if (closed) { + throw new SailException("Connection is closed", t); + } + throw t; + } + } + }); + + boolean done = false; + + ArrayDeque> futures1; + synchronized (futures) { + futures1 = new ArrayDeque<>(futures); + } + + while (!futures1.isEmpty()) { + Future future = futures1.removeFirst(); - for (Future future : futures) { assert future != null; try { + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown"); + } + if (closed) { + throw new SailException("Connection is closed"); + } if (!Thread.currentThread().isInterrupted()) { - validationResultIterators.add(future.get()); + ValidationResultIterator validationResultIterator = future.get(100, TimeUnit.MILLISECONDS); + validationResultIterators.add(validationResultIterator); } + } catch (CancellationException e) { + Thread.currentThread().interrupt(); + InterruptedException interruptedException = new InterruptedException( + "Validation future was cancelled"); + interruptedException.initCause(e); + throw interruptedException; } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof InterruptedException) { @@ -574,16 +693,24 @@ private ValidationReport performValidation(List shapes, boolea assert false; throw new IllegalStateException(cause); } + } catch (TimeoutException e) { + futures1.addLast(future); } + } if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } } finally { - for (Future future : futures) { - future.cancel(true); + var originalFutures = this.futures; + synchronized (originalFutures) { + for (Future future : originalFutures) { + future.cancel(true); + } + this.futures = List.of(); } + } if (Thread.currentThread().isInterrupted()) { @@ -618,13 +745,13 @@ void fillAddedAndRemovedStatementRepositories() throws InterruptedException { before = System.currentTimeMillis(); } - List> futures = Collections.emptyList(); + List> futures = new ArrayList<>(); boolean parallelValidation = isParallelValidation() && !addedStatementsSet.isEmpty() && !removedStatementsSet.isEmpty(); try { - futures = Stream.of(addedStatementsSet, removedStatementsSet) + Stream.of(addedStatementsSet, removedStatementsSet) .map(set -> (Callable) () -> { Set otherSet; @@ -658,6 +785,12 @@ void fillAddedAndRemovedStatementRepositories() throws InterruptedException { try (SailConnection connection = repository.getConnection()) { connection.begin(IsolationLevels.NONE); set.stream() + .peek(s -> { + if (Thread.currentThread().isInterrupted()) { + throw new SailException( + "ShacilSailConnection was interrupted while filling added/removed statement repositories"); + } + }) .filter(statement -> !otherSet.contains(statement)) .flatMap(statement -> rdfsSubClassOfReasoner == null ? Stream.of(statement) : rdfsSubClassOfReasoner.forwardChain(statement)) @@ -683,6 +816,12 @@ void fillAddedAndRemovedStatementRepositories() throws InterruptedException { if (Thread.currentThread().isInterrupted()) { return null; } + if (closed) { + throw new SailException("Connection is closed"); + } + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown"); + } if (parallelValidation) { return sail.submitToExecutorService(callable); } else { @@ -691,7 +830,8 @@ void fillAddedAndRemovedStatementRepositories() throws InterruptedException { return objectFutureTask; } }) - .collect(Collectors.toList()); + .filter(Objects::nonNull) + .forEach(futures::add); for (Future future : futures) { try { @@ -715,8 +855,10 @@ void fillAddedAndRemovedStatementRepositories() throws InterruptedException { } } finally { - for (Future future : futures) { - future.cancel(true); + if (futures != null) { + for (Future future : futures) { + future.cancel(true); + } } } @@ -736,48 +878,91 @@ synchronized public void close() throws SailException { return; } - if (getWrappedConnection() instanceof AbstractSailConnection) { - AbstractSailConnection abstractSailConnection = (AbstractSailConnection) getWrappedConnection(); + closed = true; - abstractSailConnection.waitForOtherOperations(true); + try { + var originalFutures = this.futures; + if (originalFutures != null) { + synchronized (originalFutures) { + for (Future future : futures) { + future.cancel(true); + } + this.futures = List.of(); + } + } + } finally { + try { + var originalShapeValidatorContainers = this.shapeValidatorContainers; + if (originalShapeValidatorContainers != null) { + synchronized (originalShapeValidatorContainers) { + for (ShapeValidationContainer shapeValidatorContainer : originalShapeValidatorContainers) { + try { + shapeValidatorContainer.forceClose(); + } catch (Throwable ignored) { + logger.debug("Throwable was ignored while closing connection", ignored); + } + } + shapeValidatorContainers = List.of(); + } + } + } finally { + try { + waitForOperations(); + } finally { + try { + if (readableShapesCache != null) { + readableShapesCache.close(); + readableShapesCache = null; + } + } finally { + try { + if (writableShapesCache != null) { + try { + writableShapesCache.purge(); + } finally { + writableShapesCache.close(); + writableShapesCache = null; + } + } + } finally { + innerClose(); + } + } + } + } } + } + private void innerClose() { try { - if (isActive()) { - rollback(); - } + shapesRepoConnection.close(); + } finally { try { - shapesRepoConnection.close(); + if (previousStateConnection != null) { + previousStateConnection.close(); + } } finally { try { - if (previousStateConnection != null) { - previousStateConnection.close(); + if (serializableConnection != null) { + serializableConnection.close(); } - } finally { + try { - if (serializableConnection != null) { - serializableConnection.close(); - } + super.close(); } finally { - try { - super.close(); + sail.closeConnection(); } finally { try { - sail.closeConnection(); + cleanupShapesReadWriteLock(); } finally { try { - cleanupShapesReadWriteLock(); + cleanupReadWriteLock(); } finally { - try { - cleanupReadWriteLock(); - } finally { - closed = true; - } - + cleanup(); } } } @@ -787,12 +972,32 @@ synchronized public void close() throws SailException { } } + private void waitForOperations() { + if (getWrappedConnection() instanceof AbstractSailConnection) { + AbstractSailConnection abstractSailConnection = (AbstractSailConnection) getWrappedConnection(); + + abstractSailConnection.waitForOtherOperations(true); + for (int i = 0; i < 50 && abstractSailConnection.hasActiveIterations(); i++) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + } + } + @Override public void prepare() throws SailException { if (closed) { throw new SailException("Connection is closed"); } + if (sail.isShutdown()) { + throw new SailException("Sail is shutdown"); + } + prepareHasBeenCalled = true; long before = 0; @@ -889,6 +1094,14 @@ public void prepare() throws SailException { boolean valid = invalidTuples.conforms(); + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedException("ShaclSailConnection was interrupted while validating."); + } + + if (closed) { + throw new SailException("Connection is closed"); + } + if (!valid) { throw new ShaclSailValidationException(invalidTuples); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclValidator.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclValidator.java index ed9442c0ef7..03f2177ce3e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclValidator.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclValidator.java @@ -20,6 +20,7 @@ import org.eclipse.rdf4j.common.transaction.IsolationLevels; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.vocabulary.RDF4J; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.Sail; import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.shacl.ast.ContextWithShape; @@ -122,6 +123,11 @@ private static ValidationReport performValidation(List shapes, .map(ShapeValidationContainer::performValidation) .collect(Collectors.toList()); + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException("Thread was interrupted during validation."); + } + return new LazyValidationReport(collect, 10000); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShapeValidationContainer.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShapeValidationContainer.java index 9545135c8e9..91e3b856a0e 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShapeValidationContainer.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShapeValidationContainer.java @@ -17,6 +17,7 @@ import java.util.stream.Collectors; import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.shacl.ast.Shape; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode; @@ -35,6 +36,7 @@ class ShapeValidationContainer { private final long effectiveValidationResultsLimitPerConstraint; private final boolean performanceLogging; private final Logger logger; + private volatile CloseableIteration iterator; public ShapeValidationContainer(Shape shape, Supplier planNodeSupplier, boolean logValidationExecution, boolean logValidationViolations, long effectiveValidationResultsLimitPerConstraint, @@ -124,26 +126,36 @@ public boolean hasPlanNode() { return !(planNode.isGuaranteedEmpty()); } - public ValidationResultIterator performValidation() { + public ValidationResultIterator performValidation() throws SailException { long before = getTimeStamp(); handlePreLogging(); ValidationResultIterator validationResults = null; - try (CloseableIteration iterator = planNode.iterator()) { - validationResults = new ValidationResultIterator(iterator, effectiveValidationResultsLimitPerConstraint); - return validationResults; - } catch (Throwable e) { - logger.warn("Internal error while trying to validate SHACL Shape {}", shape.getId(), e); - logger.warn("Internal error while trying to validate SHACL Shape\n{}", shape, e); - if (e instanceof Error) { + try { + try (CloseableIteration iterator = planNode.iterator()) { + this.iterator = iterator; + validationResults = new ValidationResultIterator(iterator, + effectiveValidationResultsLimitPerConstraint); + return validationResults; + } catch (InterruptedSailException e) { + logger.warn("Interrupted while validating SHACL Shape\n{}", shape, e); throw e; + } catch (Throwable e) { + logger.warn("Internal error while trying to validate SHACL Shape {}", shape.getId(), e); + logger.warn("Internal error while trying to validate SHACL Shape\n{}", shape, e); + if (e instanceof Error) { + throw e; + } + throw new SailException( + "Internal error while trying to validate SHACL Shape " + shape.getId() + "\n" + shape, e); + } finally { + handlePostLogging(before, validationResults); } - throw new SailException( - "Internal error while trying to validate SHACL Shape " + shape.getId() + "\n" + shape, e); } finally { - handlePostLogging(before, validationResults); + this.iterator = null; } + } private long getTimeStamp() { @@ -196,4 +208,15 @@ private void handlePostLogging(long before, ValidationResultIterator validationR } + public void forceClose() { + var iterator = this.iterator; + if (iterator != null) { + try { + iterator.close(); + } finally { + this.iterator = null; + } + } + } + } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/SparqlQueryParserCache.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/SparqlQueryParserCache.java index f286cbbfbbf..3e2563714c1 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/SparqlQueryParserCache.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/SparqlQueryParserCache.java @@ -52,10 +52,11 @@ public class SparqlQueryParserCache { public static TupleExpr get(String query) { try { return PARSER_QUERY_CACHE.get(query, () -> QUERY_PARSER.parseQuery(query, null).getTupleExpr()).clone(); - } catch (ExecutionException e) { + } catch (ExecutionException | UncheckedExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof MalformedQueryException) { - throw ((MalformedQueryException) e.getCause()); + logger.error("Error parsing query: \n{}", query, cause); + throw ((MalformedQueryException) cause); } if (cause instanceof RuntimeException) { throw ((RuntimeException) cause); @@ -67,12 +68,6 @@ public static TupleExpr get(String query) { throw new IllegalStateException(cause); } throw new IllegalStateException(e); - } catch (UncheckedExecutionException e) { - if (e.getCause() instanceof MalformedQueryException) { - logger.error("Error parsing query: \n{}", query, e.getCause()); - throw ((MalformedQueryException) e.getCause()); - } - throw e; } } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractBulkJoinPlanNode.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractBulkJoinPlanNode.java index 5ca6b5ca029..6eefda26973 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractBulkJoinPlanNode.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AbstractBulkJoinPlanNode.java @@ -26,7 +26,9 @@ import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor; import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailConnection; +import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.shacl.ast.SparqlQueryParserCache; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.AbstractConstraintComponent; @@ -70,6 +72,10 @@ void runQuery(Collection left, ArrayDeque righ if (!newBindindingSet.isEmpty()) { updateQuery(parsedQuery, newBindindingSet); + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(); + } executeQuery(right, connection, dataset, parsedQuery); } } @@ -115,6 +121,16 @@ private List buildBindingSets(Collection left, Sail boolean hasStatement; + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException( + "Thread was interrupted while checking previous state connection."); + } + + if (!(connection.isOpen() && connection.isActive())) { + throw new SailException("Connection is not active"); + } + if (!(tuple.getActiveTarget().isResource())) { hasStatement = previousStateConnection.hasStatement(null, null, tuple.getActiveTarget(), true, dataGraph); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AllTargetsPlanNode.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AllTargetsPlanNode.java index 4067c60f46b..a74d4dd6c2f 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AllTargetsPlanNode.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/AllTargetsPlanNode.java @@ -83,6 +83,10 @@ protected ValidationTuple loggingNext() { @Override protected boolean localHasNext() { + if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } return iterator.hasNext(); } }; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java index 6ea7f9d5e2f..73f4f49ba16 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalInnerJoin.java @@ -126,6 +126,14 @@ private void calculateNext() { parsedQuery = parseQuery(query); } + if (isClosed()) { + return; + } + + if (Thread.currentThread().isInterrupted()) { + close(); + } + runQuery(left.values(), right, connection, parsedQuery, dataset, dataGraph, skipBasedOnPreviousConnection, previousStateConnection); @@ -159,6 +167,13 @@ public void localClose() { @Override protected boolean localHasNext() { + if (isClosed()) { + return false; + } + if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } calculateNext(); return !joined.isEmpty(); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java index 14a94fe20b9..e0890381d7f 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/BulkedExternalLeftOuterJoin.java @@ -102,6 +102,14 @@ private void calculateNext() { if (parsedQuery == null) { parsedQuery = parseQuery(query); } + if (isClosed()) { + return; + } + + if (Thread.currentThread().isInterrupted()) { + close(); + return; + } runQuery(left, right, connection, parsedQuery, dataset, dataGraph, false, null); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicate.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicate.java index 279664cdace..b433434491b 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicate.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicate.java @@ -73,6 +73,12 @@ protected void init() { } void calculateNext() { + + if (Thread.currentThread().isInterrupted()) { + close(); + return; + } + if (filterOnPredicates == null) { if (!parentIterator.hasNext()) { return; @@ -92,6 +98,11 @@ void calculateNext() { } while (next == null && parentIterator.hasNext()) { + if (Thread.currentThread().isInterrupted()) { + close(); + return; + } + ValidationTuple temp = parentIterator.next(); Value subject = temp.getActiveTarget(); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicateObject.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicateObject.java index cd9dc265ca4..658490ad1f0 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicateObject.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterByPredicateObject.java @@ -31,6 +31,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.util.concurrent.UncheckedExecutionException; /** * @author Håvard Ottestad @@ -108,6 +109,11 @@ void calculateNext() { return; } + if (Thread.currentThread().isInterrupted()) { + close(); + return; + } + internResources(); if (returnMatching && (filterOnPredicate == null || filterOnObject.length == 0)) { @@ -129,6 +135,11 @@ void calculateNext() { throw new IllegalStateException("Unknown filterOn: " + filterOn); } + if (Thread.currentThread().isInterrupted()) { + close(); + return; + } + boolean matches = matches(value, filterOnPredicate, filterOnObject); if (returnMatching) { @@ -239,11 +250,14 @@ private boolean matches(Value subject, IRI filterOnPredicate, Resource[] filterO private Boolean matchesCached(Resource subject, IRI filterOnPredicate, Resource[] filterOnObject) { try { return cache.get(subject, () -> matchesUnCached(subject, filterOnPredicate, filterOnObject)); - } catch (ExecutionException e) { + } catch (ExecutionException | UncheckedExecutionException e) { if (e.getCause() != null) { if (e.getCause() instanceof RuntimeException) { throw ((RuntimeException) e.getCause()); } + if (e.getCause() instanceof Error) { + throw ((Error) e.getCause()); + } throw new SailException(e.getCause()); } throw new SailException(e); diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java index c44f511f612..2a4eddc71ed 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/FilterPlanNode.java @@ -144,6 +144,10 @@ public void close() throws SailException { @Override public boolean hasNext() throws SailException { + if (Thread.currentThread().isInterrupted() || closed) { + close(); + return false; + } calculateNext(); return next != null; } @@ -218,8 +222,8 @@ public void close() { @Override public boolean incrementIterator() { - assert !closed; - if (iterator.hasNext()) { + var iterator = this.iterator; + if (!closed && iterator != null && iterator.hasNext()) { iterator.next(); return true; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LoggingCloseableIteration.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LoggingCloseableIteration.java index d1c272cdbdc..4ecbe2a70dd 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LoggingCloseableIteration.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/LoggingCloseableIteration.java @@ -11,11 +11,17 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; +import java.util.NoSuchElementException; + import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class LoggingCloseableIteration implements CloseableIteration { + private static final Logger log = LoggerFactory.getLogger(LoggingCloseableIteration.class); private final ValidationExecutionLogger validationExecutionLogger; private final PlanNode planNode; private boolean closed; @@ -45,15 +51,41 @@ public final boolean hasNext() throws SailException { return false; } + if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } + if (!initialized) { initialized = true; - init(); + try { + init(); + } catch (InterruptedSailException e) { + Thread.currentThread().interrupt(); + close(); + return false; + } catch (Throwable t) { + close(); + throw t; + } + } - boolean hasNext = localHasNext(); + if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } + + boolean hasNext = false; + try { + hasNext = localHasNext(); + } catch (NoSuchElementException loggedOnly) { + log.trace("No more elements in iteration: {}", this.getClass(), loggedOnly); + } if (!hasNext) { - assert !localHasNext() : "Iterator was initially empty, but still has more elements! " + this.getClass(); + assert !localHasNext() || closed + : "Iterator was initially empty, but still has more elements! " + this.getClass(); close(); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PlanNode.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PlanNode.java index 82adf62ac8e..9af9725b150 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PlanNode.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/PlanNode.java @@ -12,8 +12,6 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; import org.eclipse.rdf4j.common.iteration.CloseableIteration; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.Value; /** * @author Håvard Mikkelsen Ottestad diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java index 157869cbc0d..45dfe20daa9 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Select.java @@ -12,6 +12,7 @@ package org.eclipse.rdf4j.sail.shacl.ast.planNodes; import java.util.List; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.Function; @@ -23,6 +24,7 @@ import org.eclipse.rdf4j.query.MalformedQueryException; import org.eclipse.rdf4j.query.algebra.TupleExpr; import org.eclipse.rdf4j.query.impl.EmptyBindingSet; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.memory.MemoryStoreConnection; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; @@ -58,8 +60,7 @@ public Select(SailConnection connection, SparqlFragment queryFragment, String or logger.error("Query is empty", new Throwable("This throwable is just to log the stack trace")); // empty set - fragment = "" + - "?a ?c.\n" + + fragment = "?a ?c.\n" + "FILTER (NOT EXISTS {?a ?c})"; } sorted = orderBy != null; @@ -100,7 +101,7 @@ public Select(SailConnection connection, String query, Function iterator() { return new LoggingCloseableIteration(this, validationExecutionLogger) { - CloseableIteration bindingSet; + private CloseableIteration bindingSet; protected void init() { if (bindingSet != null) { @@ -109,13 +110,46 @@ protected void init() { try { TupleExpr tupleExpr = SparqlQueryParserCache.get(query); + if (Thread.currentThread().isInterrupted()) { + close(); + return; + } + + try { + if (isClosed()) { + return; + } + if (Thread.currentThread().isInterrupted()) { + close(); + Thread.currentThread().interrupt(); + throw new InterruptedSailException("Thread was interrupted while executing SPARQL query."); + } + bindingSet = connection.evaluate(tupleExpr, dataset, EmptyBindingSet.getInstance(), true); + + } catch (Throwable t) { + if (bindingSet != null) { + bindingSet.close(); + } + throw t; + } + + if (isClosed()) { + bindingSet.close(); + } + if (Thread.currentThread().isInterrupted()) { + close(); + Thread.currentThread().interrupt(); + throw new InterruptedSailException("Thread was interrupted while executing SPARQL query."); + } - bindingSet = connection.evaluate(tupleExpr, dataset, EmptyBindingSet.getInstance(), true); if (logger.isTraceEnabled()) { boolean hasNext = bindingSet.hasNext(); logger.trace("SPARQL query (hasNext={}) \n{}", hasNext, Formatter.formatSparqlQuery(query)); } } catch (MalformedQueryException e) { + if (bindingSet != null) { + bindingSet.close(); + } if (stackTrace != null) { Exception rootCause = new Exception("Root cause"); rootCause.setStackTrace(stackTrace); @@ -123,6 +157,11 @@ protected void init() { } logger.error("Malformed query:\n{}", query); throw e; + } catch (Throwable t) { + if (bindingSet != null) { + bindingSet.close(); + } + throw t; } } @@ -135,7 +174,21 @@ public void localClose() { @Override protected boolean localHasNext() { - return bindingSet.hasNext(); + if (isClosed()) { + return false; + } + if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } + + try { + return bindingSet.hasNext(); + } catch (NoSuchElementException e) { + // This can happen if the connection is closed while we are iterating. + // In that case, we just return false. + return false; + } } @Override diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Sort.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Sort.java index 202610c1e9c..7007d772d19 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Sort.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Sort.java @@ -20,6 +20,8 @@ import org.apache.commons.text.StringEscapeUtils; import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.sail.InterruptedSailException; +import org.eclipse.rdf4j.sail.SailException; import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup; public class Sort implements PlanNode { @@ -40,23 +42,31 @@ public CloseableIteration iterator() { List sortedTuples; Iterator sortedTuplesIterator; + CloseableIteration iterator; protected void init() { assert sortedTuples == null; + checkClosedOrInterrupted(); + boolean alreadySorted; + List sortedTuples = new ArrayList<>(1); try (CloseableIteration iterator = parent.iterator()) { - sortedTuples = new ArrayList<>(1); + this.iterator = iterator; alreadySorted = true; ValidationTuple prev = null; while (iterator.hasNext()) { + checkClosedOrInterrupted(); ValidationTuple next = iterator.next(); + checkClosedOrInterrupted(); + sortedTuples.add(next); // quick break out if sortedTuples is guaranteed to be of size 1 since we don't need to sort // it then if (sortedTuples.size() == 1 && !iterator.hasNext()) { + this.sortedTuples = sortedTuples; sortedTuplesIterator = sortedTuples.iterator(); return; } @@ -65,8 +75,10 @@ protected void init() { alreadySorted = false; } prev = next; - } + checkClosedOrInterrupted(); + } + this.iterator = null; assert !iterator.hasNext() : "Iterator: " + iterator; } @@ -80,10 +92,23 @@ protected void init() { } } + this.sortedTuples = sortedTuples; sortedTuplesIterator = sortedTuples.iterator(); } + private void checkClosedOrInterrupted() { + if (Thread.currentThread().isInterrupted()) { + close(); + Thread.currentThread().interrupt(); + throw new InterruptedSailException("Thread was interrupted while sorting."); + } + if (isClosed()) { + throw new SailException("Iterator was closed while sorting."); + } + + } + @Override protected boolean localHasNext() { return sortedTuplesIterator.hasNext(); @@ -98,6 +123,10 @@ protected ValidationTuple loggingNext() { public void localClose() { sortedTuplesIterator = Collections.emptyIterator(); sortedTuples = null; + var iterator = this.iterator; + if (iterator != null) { + iterator.close(); + } } }; diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnionNode.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnionNode.java index ab21b5dc358..fa8f7ece621 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnionNode.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/UnionNode.java @@ -137,6 +137,17 @@ private void calculateNext() { for (int i = 0; i < peekList.length; i++) { if (peekList[i] == null) { var iterator = iterators[i]; + if (iterator == null) { + if (isClosed()) { + return; + } + if (Thread.currentThread().isInterrupted()) { + close(); + return; + } + // It is very unlikely that the iterator is null when we are not closed or interrupted, so + // in those cases we just let it throw a NullPointerException + } if (iterator.hasNext()) { peekList[i] = iterator.next(); } @@ -196,6 +207,10 @@ public void localClose() { @Override protected boolean localHasNext() { + if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } calculateNext(); return next != null; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Unique.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Unique.java index d412a74b1a8..23e5a470d91 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Unique.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/Unique.java @@ -185,6 +185,14 @@ public void localClose() { @Override protected boolean localHasNext() { + if (isClosed()) { + return false; + } + if (Thread.currentThread().isInterrupted()) { + close(); + return false; + } + calculateNext(); return next != null; } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java index f208b619303..c21588fbcea 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/planNodes/ValidationTuple.java @@ -26,6 +26,7 @@ import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.query.BindingSet; import org.eclipse.rdf4j.query.algebra.evaluation.util.ValueComparator; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents.ConstraintComponent; import org.eclipse.rdf4j.sail.shacl.results.ValidationResult; import org.slf4j.Logger; @@ -155,6 +156,11 @@ public ConstraintComponent.Scope getScope() { public int compareActiveTarget(ValidationTuple other) { + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException("Thread was interrupted"); + } + Value left = getActiveTarget(); Value right = other.getActiveTarget(); @@ -163,6 +169,11 @@ public int compareActiveTarget(ValidationTuple other) { public int compareFullTarget(ValidationTuple other) { + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException("Thread was interrupted"); + } + int min = Math.min(getFullChainSize(false), other.getFullChainSize(false)); List targetChain = getTargetChain(false); @@ -219,7 +230,7 @@ public String toString() { if (v == getActiveTarget()) { return "T–" + v; } else if (v == getValue()) { - return "V–" + v + ""; + return "V–" + v; } return v.stringValue(); }).collect(Collectors.joining(" -> ")) + " }, propertyShapeScopeWithValue=" + propertyShapeScopeWithValue + diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java index 5d44319bee3..3269c690b4a 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ast/targets/TargetChainRetriever.java @@ -42,6 +42,7 @@ import org.eclipse.rdf4j.query.parser.ParsedQuery; import org.eclipse.rdf4j.query.parser.QueryParserFactory; import org.eclipse.rdf4j.query.parser.QueryParserRegistry; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment; import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher; @@ -229,6 +230,15 @@ public void calculateNextStatementMatcher() { throw new IllegalStateException("currentVarNames should not be empty!"); } + if (Thread.currentThread().isInterrupted()) { + Thread.currentThread().interrupt(); + throw new InterruptedSailException(); + } + + if (isClosed()) { + return; + } + statements = connection.getStatements( currentStatementMatcher.getSubjectValue(), currentStatementMatcher.getPredicateValue(), diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java index 495bbebbb24..02bc66025da 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/results/lazy/ValidationResultIterator.java @@ -21,6 +21,7 @@ import org.eclipse.rdf4j.common.annotation.InternalUseOnly; import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.common.iteration.CloseableIteratorIteration; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple; import org.eclipse.rdf4j.sail.shacl.results.ValidationResult; @@ -40,7 +41,6 @@ public ValidationResultIterator(CloseableIteration tu this.limit = limit; this.tupleIterator = tupleIterator; getTuples(); - } private void calculateNext() { @@ -90,6 +90,11 @@ public List getTuples() { List actualList = new ArrayList<>(); long localCounter = 0; while (tupleIterator.hasNext() && (limit < 0 || localCounter++ < limit + 1)) { + if (Thread.currentThread().isInterrupted()) { + tupleIterator.close(); + Thread.currentThread().interrupt(); + throw new InterruptedSailException("Thread was interrupted during validation"); + } actualList.add(tupleIterator.next()); } diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/wrapper/data/ConnectionsGroup.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/wrapper/data/ConnectionsGroup.java index 605810c5b49..b67fff20fd2 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/wrapper/data/ConnectionsGroup.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/wrapper/data/ConnectionsGroup.java @@ -35,6 +35,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.util.concurrent.UncheckedExecutionException; /** * @apiNote since 3.0. This feature is for internal use only: its existence, signature or behavior may change without @@ -165,7 +166,14 @@ public T getSailSpecificValue(T value, StatementPosition state }); return ((T) t); - } catch (ExecutionException e) { + } catch (ExecutionException | UncheckedExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } + if (cause instanceof Error) { + throw (Error) cause; + } throw new SailException(e); } } diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java index 9a3d4f8afa4..33b43c19ea5 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/AbstractShaclTest.java @@ -375,17 +375,21 @@ void runTestCase(TestCase testCase, IsolationLevel isolationLevel, boolean prelo connection.begin(isolationLevel); connection.prepareUpdate(query).execute(); printCurrentState(connection); - connection.commit(); - } catch (RepositoryException sailException) { - if (!(sailException.getCause() instanceof ShaclSailValidationException)) { - throw sailException; + try { + connection.commit(); + + } catch (RepositoryException sailException) { + if (!(sailException.getCause() instanceof ShaclSailValidationException)) { + throw sailException; + } + + Assertions.assertEquals(testCaseQueries.get(testCaseQueries.size() - 1), queryFile, + "Validation should only fail on the very last query"); + exception = true; + logger.debug(sailException.getMessage()); + printResults(sailException); + connection.rollback(); } - - Assertions.assertEquals(testCaseQueries.get(testCaseQueries.size() - 1), queryFile, - "Validation should only fail on the very last query"); - exception = true; - logger.debug(sailException.getMessage()); - printResults(sailException); } } catch (IOException e) { e.printStackTrace(); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ExtendedFeaturesetTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ExtendedFeaturesetTest.java index 279c628bdfe..6e4ce2f325b 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ExtendedFeaturesetTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ExtendedFeaturesetTest.java @@ -68,6 +68,7 @@ public void testThatDashCanBeEnabled() throws Throwable { try { connection.commit(); } catch (RepositoryException e) { + connection.rollback(); throw e.getCause(); } }); @@ -117,6 +118,7 @@ public void testThatTargetShapesCanBeEnabled() throws Throwable { try { connection.commit(); } catch (RepositoryException e) { + connection.rollback(); throw e.getCause(); } }); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/PrepareCommitTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/PrepareCommitTest.java index 728fc9743e2..ce6a651fc73 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/PrepareCommitTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/PrepareCommitTest.java @@ -34,6 +34,7 @@ import org.eclipse.rdf4j.rio.RDFFormat; import org.eclipse.rdf4j.rio.Rio; import org.eclipse.rdf4j.sail.NotifyingSailConnection; +import org.eclipse.rdf4j.sail.SailException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -257,4 +258,79 @@ public void testAutomaticRollbackRepository() throws IOException { Assertions.assertTrue(exception); } + @Test + public void testShutdownBeforePrepare() throws IOException, InterruptedException { + ShaclSail shaclSail = Utils.getInitializedShaclSail("shacl.trig"); + Thread thread = new Thread(shaclSail::shutDown); + + try (NotifyingSailConnection connection = shaclSail.getConnection()) { + // due to optimizations in the ShaclSail, changes after prepare has run will only be detected if there is + // data in the base sail already! + connection.begin(); + connection.addStatement(RDFS.RESOURCE, RDFS.LABEL, SimpleValueFactory.getInstance().createLiteral("label")); + connection.commit(); + + connection.begin(); + connection.addStatement(RDFS.RESOURCE, RDFS.SUBCLASSOF, RDFS.RESOURCE); + thread.start(); + Thread.sleep(500); + + assertThrows(SailException.class, () -> { + try { + connection.prepare(); + } catch (RepositoryException e) { + throw e.getCause(); + } + }); + + assertThrows(SailException.class, () -> { + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e.getCause(); + } catch (SailException e) { + connection.rollback(); + throw e; + } + }); + + } finally { + try { + thread.join(); + } catch (Exception e) { + } + + shaclSail.shutDown(); + } + + } + + @Test + public void testShutdownBeforeCommit() throws IOException, InterruptedException { + ShaclSail shaclSail = Utils.getInitializedShaclSail("shacl.trig"); + Thread thread = new Thread(shaclSail::shutDown); + + try (NotifyingSailConnection connection = shaclSail.getConnection()) { + // due to optimizations in the ShaclSail, changes after prepare has run will only be detected if there is + // data in the base sail already! + connection.begin(); + connection.addStatement(RDFS.RESOURCE, RDFS.LABEL, SimpleValueFactory.getInstance().createLiteral("label")); + connection.commit(); + + connection.begin(); + connection.addStatement(RDFS.RESOURCE, RDFS.SUBCLASSOF, RDFS.RESOURCE); + connection.prepare(); + + thread.start(); + + connection.commit(); + + } finally { + thread.join(); + shaclSail.shutDown(); + } + + } + } diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShapesGraphTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShapesGraphTest.java index 554c127af4a..9a3ae7f6d55 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShapesGraphTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShapesGraphTest.java @@ -84,7 +84,12 @@ public void testInvalid() throws Throwable { try (RepositoryConnection connection = repository.getConnection()) { connection.begin(); connection.add(laura, RDF.TYPE, FOAF.PERSON, data2); - connection.commit(); + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e; + } } }); @@ -112,7 +117,12 @@ public void testInvalidUnionGraph() throws Throwable { connection.add(laura, FOAF.PHONE, Values.literal(2), data1); connection.add(laura, FOAF.PHONE, Values.literal(3), data2); - connection.commit(); + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e; + } } }); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShutdownDuringValidationIT.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShutdownDuringValidationIT.java new file mode 100644 index 00000000000..d36301ba5dc --- /dev/null +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShutdownDuringValidationIT.java @@ -0,0 +1,535 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + ******************************************************************************/ + +package org.eclipse.rdf4j.sail.shacl; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.stream.IntStream; + +import org.eclipse.rdf4j.model.BNode; +import org.eclipse.rdf4j.model.IRI; +import org.eclipse.rdf4j.model.Model; +import org.eclipse.rdf4j.model.ValueFactory; +import org.eclipse.rdf4j.model.vocabulary.DCAT; +import org.eclipse.rdf4j.model.vocabulary.DCTERMS; +import org.eclipse.rdf4j.model.vocabulary.RDF; +import org.eclipse.rdf4j.model.vocabulary.RDFS; +import org.eclipse.rdf4j.repository.RepositoryException; +import org.eclipse.rdf4j.repository.sail.SailRepository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.rio.RDFFormat; +import org.eclipse.rdf4j.rio.Rio; +import org.eclipse.rdf4j.sail.InterruptedSailException; +import org.eclipse.rdf4j.sail.SailException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.parallel.Execution; +import org.junit.jupiter.api.parallel.ExecutionMode; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Tag("slow") +@Execution(ExecutionMode.CONCURRENT) +public class ShutdownDuringValidationIT { + + private static final Logger logger = LoggerFactory + .getLogger(ShutdownDuringValidationIT.class); + + private static final Model realData = getRealData(); + public static final int EXPECTED_REPOSITORY_SIZE = 613157; + private static long MAX_MILLIS = Long.MIN_VALUE; + + private SailRepository repository; + + private static Model getRealData() { + ClassLoader classLoader = ShutdownDuringValidationIT.class.getClassLoader(); + + try { + try (InputStream inputStream = new BufferedInputStream( + classLoader.getResourceAsStream("complexBenchmark/datagovbe-valid.ttl"))) { + return Rio.parse(inputStream, RDFFormat.TURTLE); + } + } catch (IOException e) { + throw new RuntimeException(e); + } catch (NullPointerException e) { + throw new RuntimeException("Could not load file: benchmarkFiles/datagovbe-valid.ttl", e); + } + } + + @BeforeAll + static void beforeAll() throws IOException { + for (int i = 0; i < 5; i++) { + long start = System.currentTimeMillis(); + var repository = new SailRepository(Utils.getInitializedShaclSail("complexBenchmark/shacl.trig")); + try { + try (var connection = repository.getConnection()) { + connection.begin(ShaclSail.TransactionSettings.PerformanceHint.ParallelValidation); + connection.add(realData); + connection.commit(); + } + + } finally { + repository.shutDown(); + } + MAX_MILLIS = Math.max(MAX_MILLIS, (long) ((System.currentTimeMillis() - start) * 1.1)); + } + } + + @BeforeEach + void setUp() throws IOException { + assertFalse(Thread.interrupted()); + repository = new SailRepository(Utils.getInitializedShaclSail("complexBenchmark/shacl.trig")); + ((ShaclSail) repository.getSail()).setTransactionalValidationLimit(1000000); + ((ShaclSail) repository.getSail()).setConnectionTimeOut((int) (MAX_MILLIS + 10)); + } + + @AfterEach + void tearDown() { + if (repository != null) { + repository.shutDown(); + repository = null; + } + // clear the interrupt flag if it was set + Thread.interrupted(); + } + + @ParameterizedTest + @MethodSource("sleepTimes") + public void shutdownDuringValidation(int sleepMillis) { + try { +// clear interrupted flag + Thread.interrupted(); + + Thread thread; + try (var connection = repository.getConnection()) { + connection.begin(ShaclSail.TransactionSettings.PerformanceHint.ParallelValidation); + connection.add(realData); + thread = startShutdownThread(sleepMillis); + + commitAndExpect(connection, EXPECTED_REPOSITORY_SIZE, 0); + + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + return; + } + logger.error("Error during test execution", e); + throw e; + } + + waitForThread(thread); + + try (var connection = repository.getConnection()) { + long size = connection.size(); + if (size > 0) { + assertEquals(EXPECTED_REPOSITORY_SIZE, size, + "The repository should either be empty or contain the expected data after shutdown during validation"); + } else { + assertEquals(0, size, "The repository should be empty after shutdown during validation"); + } + + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } else { + throw e; + } + } + } catch (Exception e) { + if (e instanceof RepositoryException && causedByInterruptedException(e)) { + System.out.println(e); + return; + } + if (e instanceof RepositoryException && e.getCause() instanceof InterruptedSailException) { + System.out.println(e); + return; + } + if (e.toString().contains("closed")) { + System.out.println(e); + return; + } + throw e; + } + } + + @ParameterizedTest + @MethodSource("sleepTimes") + public void shutdownDuringValidationTransactional(int sleepMillis) { + try { +// clear interrupted flag + Thread.interrupted(); + + Thread thread; + try (var connection = repository.getConnection()) { + connection.begin(); + ValueFactory vf = connection.getValueFactory(); + BNode bnode = vf.createBNode(); + connection.add(bnode, RDF.TYPE, RDFS.RESOURCE); + connection.commit(); + } + + try (var connection = repository.getConnection()) { + connection.begin(ShaclSail.TransactionSettings.PerformanceHint.ParallelValidation); + connection.add(realData); + thread = startShutdownThread(sleepMillis); + + commitAndExpect(connection, EXPECTED_REPOSITORY_SIZE + 1, 1); + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } + logger.error("Error during test execution", e); + throw e; + } + + waitForThread(thread); + + try (var connection = repository.getConnection()) { + long size = connection.size(); + + assertThat(size) + .as("Repository size") + .isIn(0L, 1L, (long) (EXPECTED_REPOSITORY_SIZE + 1)); + + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } else { + throw e; + } + } + } catch (Exception e) { + if (e instanceof RepositoryException && causedByInterruptedException(e)) { + System.out.println(e); + return; + } + if (e instanceof RepositoryException && e.getCause() instanceof InterruptedSailException) { + System.out.println(e); + return; + } + if (e.toString().contains("closed")) { + System.out.println(e); + return; + } + throw e; + } + } + + @ParameterizedTest + @MethodSource("sleepTimes") + public void shutdownDuringValidationFailure(int sleepMillis) { + try { + +// clear interrupted flag + Thread.interrupted(); + + Thread thread; + + try (var connection = repository.getConnection()) { + connection.begin(ShaclSail.TransactionSettings.PerformanceHint.ParallelValidation); + connection.add(realData); + ValueFactory vf = connection.getValueFactory(); + IRI iri = vf.createIRI("http://example.com/node1"); + connection.add(iri, RDF.TYPE, DCAT.DATASET); + connection.add(iri, DCTERMS.ACCESS_RIGHTS, vf.createLiteral("")); + thread = startShutdownThread(sleepMillis); + + commitAndExpect(connection, 0, 0); + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } + logger.error("Error during test execution", e); + throw e; + } + waitForThread(thread); + + try (var connection = repository.getConnection()) { + long size = connection.size(); + assertEquals(0, size, + "The repository should be empty because the transaction always fails validation."); + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } else { + throw e; + } + } + } catch (Exception e) { + if (e instanceof RepositoryException && causedByInterruptedException(e)) { + System.out.println(e); + return; + } + if (e instanceof RepositoryException && e.getCause() instanceof InterruptedSailException) { + System.out.println(e); + return; + } + if (e.toString().contains("closed")) { + System.out.println(e); + return; + } + throw e; + } + } + + @ParameterizedTest + @MethodSource("sleepTimes") + public void shutdownDuringValidationFailureNonParallel(int sleepMillis) { + try { +// clear interrupted flag + Thread.interrupted(); + + Thread thread; + + try (var connection = repository.getConnection()) { + connection.begin(ShaclSail.TransactionSettings.PerformanceHint.SerialValidation); + connection.add(realData); + ValueFactory vf = connection.getValueFactory(); + IRI iri = vf.createIRI("http://example.com/node1"); + connection.add(iri, RDF.TYPE, DCAT.DATASET); + connection.add(iri, DCTERMS.ACCESS_RIGHTS, vf.createLiteral("")); + thread = startShutdownThread(sleepMillis); + + commitAndExpect(connection, 0, 0); + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } + logger.error("Error during test execution", e); + throw e; + } + + waitForThread(thread); + + try (var connection = repository.getConnection()) { + long size = connection.size(); + assertEquals(0, size, + "The repository should be empty because the transaction always fails validation."); + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } else { + throw e; + } + } + } catch (Exception e) { + if (e instanceof RepositoryException && causedByInterruptedException(e)) { + System.out.println(e); + return; + } + if (e instanceof RepositoryException && e.getCause() instanceof InterruptedSailException) { + System.out.println(e); + return; + } + if (e.toString().contains("closed")) { + System.out.println(e); + return; + } + + throw e; + } + } + + @ParameterizedTest + @MethodSource("sleepTimes") + public void shutdownDuringValidationTransactionalNonParallel(int sleepMillis) { + try { + // clear interrupted flag + boolean interrupted = Thread.interrupted(); + + Thread thread; + try (var connection = repository.getConnection()) { + connection.begin(); + ValueFactory vf = connection.getValueFactory(); + BNode iri = vf.createBNode(); + connection.add(iri, RDF.TYPE, RDFS.RESOURCE); + connection.commit(); + } + + try (var connection = repository.getConnection()) { + connection.begin(ShaclSail.TransactionSettings.PerformanceHint.SerialValidation); + connection.add(realData); + thread = startShutdownThread(sleepMillis); + + commitAndExpect(connection, EXPECTED_REPOSITORY_SIZE + 1, 1); + } catch (RepositoryException | SailException e) { + System.out.println(e); + if (e instanceof InterruptedSailException) { + // ignore this exception + return; + } + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } + if (e.getCause() instanceof InterruptedSailException) { + // ignore this exception + return; + } + + logger.error("Error during test execution", e); + throw e; + } + + waitForThread(thread); + + try (var connection = repository.getConnection()) { + long size = connection.size(); + assertThat(size) + .as("Repository size") + .isIn(0L, 1L, (long) (EXPECTED_REPOSITORY_SIZE + 1)); + } catch (RepositoryException e) { + if (causedByInterruptedException(e)) { + // ignore this exception + return; + } else { + throw e; + } + } + } catch (Exception e) { + if (e instanceof RepositoryException && causedByInterruptedException(e)) { + System.out.println(e); + return; + } + if (e instanceof RepositoryException && e.getCause() instanceof InterruptedSailException) { + System.out.println(e); + return; + } + if (e.toString().contains("closed")) { + System.out.println(e); + return; + } + throw e; + } + } + + @Test + void nestedInterruptedExceptionShouldBeDetected() { + InterruptedException interruptedException = new InterruptedException("nested"); + SailException sailException = new SailException("wrapper", new SailException("inner", interruptedException)); + RepositoryException repositoryException = new RepositoryException("top", sailException); + + boolean handled = causedByInterruptedException(repositoryException); + + assertThat(handled).as("Should detect nested InterruptedException").isTrue(); + } + + private static boolean causedByInterruptedException(Throwable throwable) { + return causedByInterruptedExceptionRecursion(throwable, 10); + } + + private static boolean causedByInterruptedExceptionRecursion(Throwable throwable, int maxDepth) { + if (maxDepth < 0) { + throw new IllegalStateException("Too deep"); + } + if (throwable == null) { + return false; + } + if (throwable instanceof InterruptedException) { + return true; + } + if (throwable instanceof InterruptedSailException) { + return true; + } + if (throwable.getCause() == throwable) { + return false; + } + return causedByInterruptedExceptionRecursion(throwable.getCause(), maxDepth - 1); + } + + private static void commitAndExpect(SailRepositoryConnection connection, long expected, long failedExpected) { + try { + connection.commit(); + assertFalse(Thread.currentThread().isInterrupted(), "The thread should not have been interrupted"); + long size = connection.size(); + assertEquals(expected, size, "The repository should be empty after shutdown during validation"); + } catch (IllegalStateException e) { + if (!e.getMessage().contains("closed")) { + throw e; + } + } catch (RepositoryException ignored) { + System.out.println(ignored.getMessage()); + try { + connection.rollback(); + } catch (Exception e) { + if (causedByInterruptedException(e)) { + // ignore this exception + } else { + throw e; + } + } + try { + long size = connection.size(); + if (size != 0) { + assertEquals(failedExpected, size, + "The repository should be at the initial state after shutdown during validation and rollback."); + } + + } catch (RepositoryException e) { + if (e.toString().contains("closed")) { + return; + } + throw e; + } + + } + } + + private static void waitForThread(Thread thread) { + int i = 0; + while (thread.isAlive() && i++ < 1000) { + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } + } + + private Thread startShutdownThread(int sleepMillis) { + Thread thread; + thread = new Thread(() -> { + try { + Thread.sleep(sleepMillis); + repository.shutDown(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + thread.start(); + return thread; + } + + private static IntStream sleepTimes() { + if (MAX_MILLIS <= 0) { + throw new IllegalStateException("MAX_MILLIS must be set to a positive value before running tests."); + } + int step = ((int) (MAX_MILLIS / 20)); + + return IntStream.iterate(1, n -> n <= MAX_MILLIS, n -> n + step); + } +} diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TempTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TempTest.java index 3ea6db9ed81..acedc032b54 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TempTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TempTest.java @@ -129,6 +129,7 @@ public void maxCount() throws Exception { try { connection.commit(); } catch (RepositoryException e) { + connection.rollback(); throw e.getCause(); } }); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TestStartLoggerExtension.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TestStartLoggerExtension.java new file mode 100644 index 00000000000..09eecab80bf --- /dev/null +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TestStartLoggerExtension.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright (c) 2025 Eclipse RDF4J contributors. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + *******************************************************************************/ +package org.eclipse.rdf4j.sail.shacl; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * JUnit 5 extension that prints a line to stdout when a test starts (and ends). This helps identify which test is + * currently running if the suite hangs. + */ +public class TestStartLoggerExtension + implements BeforeEachCallback, BeforeTestExecutionCallback, TestWatcher { + + private static final Logger logger = LoggerFactory.getLogger(TestStartLoggerExtension.class); + + private static void print(String phase, ExtensionContext context) { + String cls = context.getTestClass().map(Class::getName).orElse(""); + String method = context.getTestMethod().map(m -> m.getName()).orElse(""); + String display = context.getDisplayName(); + System.out.println("[TEST] " + phase + ": " + cls + "#" + method + " (" + display + ")"); + System.out.flush(); +// context.publishReportEntry("[TEST] " + phase + ": " + cls + "#" + method + " (" + display + ")"); + } + + @Override + public void beforeEach(ExtensionContext context) { + // Earliest per-test hook before any user @BeforeEach methods + print("BeforeEach", context); + } + + @Override + public void beforeTestExecution(ExtensionContext context) { + // Immediately before the test method executes + print("Start", context); + } + + @Override + public void testSuccessful(ExtensionContext context) { + print("Success", context); + } + + @Override + public void testFailed(ExtensionContext context, Throwable cause) { + print("Failed: " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), context); + } + + @Override + public void testAborted(ExtensionContext context, Throwable cause) { + print("Aborted: " + (cause == null ? "" : cause.getClass().getSimpleName()), context); + } +} diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionSettingsTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionSettingsTest.java index 8506d0f1bff..b31ddc09663 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionSettingsTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionSettingsTest.java @@ -329,6 +329,7 @@ public void testInvalid() throws Throwable { try { connection.commit(); } catch (RepositoryException e) { + connection.rollback(); throw e.getCause(); } }); @@ -357,6 +358,7 @@ public void testInvalidSnapshot() throws Throwable { try { connection.commit(); } catch (RepositoryException e) { + connection.rollback(); throw e.getCause(); } }); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionValidationLimitTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionValidationLimitTest.java index a05b28df21f..7ba06894e63 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionValidationLimitTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionValidationLimitTest.java @@ -142,6 +142,7 @@ public void testFailoverToBulkValidationTriggersValidation() throws Exception { try { connection.commit(); } catch (RepositoryException repositoryException) { + connection.rollback(); throw repositoryException.getCause(); } }); diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionalIsolationSlowIT.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionalIsolationSlowIT.java index 53583ff0a4e..23a954966db 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionalIsolationSlowIT.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TransactionalIsolationSlowIT.java @@ -79,6 +79,7 @@ public void testIsolationMultithreaded_READ_COMMITTED() throws Throwable { try { connection.commit(); } catch (Throwable ignored) { + connection.rollback(); } } @@ -148,6 +149,7 @@ public void testIsolationMultithreaded_READ_COMMITTED() throws Throwable { try { connection.commit(); } catch (Throwable ignored) { + connection.rollback(); } } @@ -216,6 +218,8 @@ public void testIsolationMultithreaded_SNAPSHOT() throws Throwable { try { connection.commit(); } catch (Throwable ignored) { + connection.rollback(); + } } @@ -285,6 +289,8 @@ public void testIsolationMultithreaded_SNAPSHOT() throws Throwable { try { connection.commit(); } catch (Throwable ignored) { + connection.rollback(); + } } diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TruncatedValidationReportTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TruncatedValidationReportTest.java index e9787dea31b..cadf69da643 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TruncatedValidationReportTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/TruncatedValidationReportTest.java @@ -313,6 +313,7 @@ private ValidationReport getValidationReport(SailRepository shaclRepository) { connection.commit(); } catch (RepositoryException e) { validationReport = ((ShaclSailValidationException) e.getCause()).getValidationReport(); + connection.rollback(); } } return validationReport; diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ValidationReportTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ValidationReportTest.java index ee90e5c5eb7..b251463aa23 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ValidationReportTest.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ValidationReportTest.java @@ -45,7 +45,13 @@ public void simpleFirstTest() throws IOException { connection.add(RDFS.CLASS, RDF.TYPE, RDFS.RESOURCE); connection.add(RDF.SUBJECT, RDF.TYPE, RDFS.RESOURCE); - connection.commit(); + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e; + } + Assertions.fail(); } catch (RepositoryException e) { @@ -103,8 +109,12 @@ public void withoutPathTest() throws IOException { connection.begin(); connection.add(vf.createIRI("http://example.com/ns#", "node1"), RDF.TYPE, vf.createIRI("http://example.com/ns#", "SecondTarget")); - connection.commit(); - + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e; + } Assertions.fail(); } catch (RepositoryException e) { @@ -157,7 +167,12 @@ public void nestedLogicalOrSupport() throws IOException { connection.prepareUpdate(IOUtils.toString(ValidationReportTest.class.getClassLoader() .getResourceAsStream("test-cases/or/datatype/invalid/case1/query1.rq"), StandardCharsets.UTF_8)) .execute(); - connection.commit(); + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e; + } Assertions.fail(); } catch (RepositoryException e) { @@ -222,7 +237,12 @@ public void testHasValueIn() throws IOException { .getResourceAsStream("test-cases/hasValueIn/simple/invalid/case1/query1.rq"), StandardCharsets.UTF_8)) .execute(); - connection.commit(); + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e; + } Assertions.fail(); } catch (RepositoryException e) { @@ -280,7 +300,12 @@ public void testHasValue() throws IOException { .getResourceAsStream("test-cases/hasValue/simple/invalid/case1/query1.rq"), StandardCharsets.UTF_8)) .execute(); - connection.commit(); + try { + connection.commit(); + } catch (RepositoryException e) { + connection.rollback(); + throw e; + } Assertions.fail(); } catch (RepositoryException e) { diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/MaxCountSparqlBenchmarkEmpty.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/MaxCountSparqlBenchmarkEmpty.java index 9009fa2cd53..46f3169c3b1 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/MaxCountSparqlBenchmarkEmpty.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/MaxCountSparqlBenchmarkEmpty.java @@ -243,6 +243,7 @@ public void shaclBulkSparql() throws Exception { try { connection.commit(); } catch (RepositoryException e) { + connection.rollback(); if (!(e.getCause() instanceof ShaclSailValidationException)) { throw e; } @@ -288,6 +289,7 @@ public void shaclBulkNonSparql() throws Exception { try { connection.commit(); } catch (RepositoryException e) { + connection.rollback(); if (!(e.getCause() instanceof ShaclSailValidationException)) { throw e; } diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/ParallelBenchmark.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/ParallelBenchmark.java index fddf75766c4..e3467bd71fb 100644 --- a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/ParallelBenchmark.java +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/benchmark/ParallelBenchmark.java @@ -242,7 +242,7 @@ private void runBenchmark(IsolationLevels isolationLevel, SailRepository reposit connection.commit(); success = true; } catch (RepositoryException ignored) { - + connection.rollback(); } } } diff --git a/core/sail/shacl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/core/sail/shacl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension new file mode 100644 index 00000000000..a3676b23ac6 --- /dev/null +++ b/core/sail/shacl/src/test/resources/META-INF/services/org.junit.jupiter.api.extension.Extension @@ -0,0 +1 @@ +org.eclipse.rdf4j.sail.shacl.TestStartLoggerExtension diff --git a/core/sail/shacl/src/test/resources/junit-platform.properties b/core/sail/shacl/src/test/resources/junit-platform.properties index eea5fb2a963..5f3e14bbeb4 100644 --- a/core/sail/shacl/src/test/resources/junit-platform.properties +++ b/core/sail/shacl/src/test/resources/junit-platform.properties @@ -1,3 +1,8 @@ junit.jupiter.execution.parallel.mode.default = same_thread junit.jupiter.execution.parallel.mode.classes.default = concurrent junit.jupiter.execution.parallel.enabled = true +junit.jupiter.execution.parallel.config.strategy = fixed +junit.jupiter.execution.parallel.config.fixed.parallelism = 4 + +junit.jupiter.execution.timeout.default = 10m +junit.jupiter.execution.timeout.mode = disabled_on_debug diff --git a/core/sail/solr/pom.xml b/core/sail/solr/pom.xml index 8f5be7b29bb..ff6f5db3d4c 100644 --- a/core/sail/solr/pom.xml +++ b/core/sail/solr/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-sail - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-solr RDF4J: Solr Sail Index diff --git a/core/sparqlbuilder/pom.xml b/core/sparqlbuilder/pom.xml index 945ce2966db..af6e8779b7b 100644 --- a/core/sparqlbuilder/pom.xml +++ b/core/sparqlbuilder/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sparqlbuilder RDF4J: SparqlBuilder diff --git a/core/spin/pom.xml b/core/spin/pom.xml index 6f333065d8d..09cdea71032 100644 --- a/core/spin/pom.xml +++ b/core/spin/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-spin RDF4J: SPIN diff --git a/core/storage/pom.xml b/core/storage/pom.xml index fd1e564e943..54e506aa367 100644 --- a/core/storage/pom.xml +++ b/core/storage/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-core - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-storage RDF4J: Storage Libraries diff --git a/examples/pom.xml b/examples/pom.xml index a85565a5a24..ac6224a81f3 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -7,7 +7,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT diff --git a/pom.xml b/pom.xml index e7495e3550e..d032d63fd1c 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT pom Eclipse RDF4J An extensible Java framework for RDF and SPARQL @@ -446,7 +446,7 @@ org.apache.commons commons-lang3 - 3.12.0 + 3.18.0 org.apache.httpcomponents @@ -628,7 +628,7 @@ no.hasmac hasmac-json-ld - 0.9.0 + 0.10.1 @@ -811,19 +811,19 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + 3.5.4 - -Xmx2048M + -Xmx2G org.apache.maven.plugins maven-failsafe-plugin - 3.2.5 + 3.5.4 1 false - -Xmx1G + -Xmx2G **/*IT.java diff --git a/site/content/download.md b/site/content/download.md index 0a5a358a0bd..bb12fd57809 100644 --- a/site/content/download.md +++ b/site/content/download.md @@ -5,15 +5,15 @@ toc: true You can either retrieve RDF4J via Apache Maven, or download the SDK or onejar directly. -## RDF4J 5.1.5 (latest) +## RDF4J 5.1.6 (latest) -RDF4J 5.1.5 is our latest stable release. It requires Java 11 minimally. -For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/5.1.5). +RDF4J 5.1.6 is our latest stable release. It requires Java 11 minimally. +For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/5.1.6). -- [RDF4J 5.1.5 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.1.5-sdk.zip)
+- [RDF4J 5.1.6 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.1.6-sdk.zip)
Full Eclipse RDF4J SDK, containing all libraries, RDF4J Server, Workbench, and Console applications, and Javadoc API. -- [RDF4J 5.1.5 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.1.5-onejar.jar)
+- [RDF4J 5.1.6 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.1.6-onejar.jar)
Single jar file for easy inclusion of the full RDF4J toolkit in your Java project. - [RDF4J artifacts](https://search.maven.org/search?q=org.eclipse.rdf4j) on the [Maven Central Repository](http://search.maven.org/) @@ -28,7 +28,7 @@ You can include RDF4J as a Maven dependency in your Java project by including th org.eclipse.rdf4j rdf4j-bom - 5.1.5 + 5.1.6 pom import diff --git a/site/content/news/rdf4j-516.md b/site/content/news/rdf4j-516.md new file mode 100644 index 00000000000..c1bd18d7751 --- /dev/null +++ b/site/content/news/rdf4j-516.md @@ -0,0 +1,14 @@ +--- +title: "RDF4J 5.1.6 released" +date: 2025-09-24T22:00:30+0200 +layout: "single" +categories: ["news"] +--- +RDF4J 5.1.6 is now available. This is a patch release fixing 12 bugs. + +For more details, have a look at the [release notes](/release-notes/5.1.6). + +### Links + +- [Download RDF4J](/download/) +- [release notes](/release-notes/5.1.6). diff --git a/site/content/release-notes/5.1.6.md b/site/content/release-notes/5.1.6.md new file mode 100644 index 00000000000..a05ed405c94 --- /dev/null +++ b/site/content/release-notes/5.1.6.md @@ -0,0 +1,11 @@ +--- +title: "5.1.6" +toc: true +--- +RDF4J 5.1.6 is a patch release that fixes 12 issues. + +For a complete overview, see [all issues fixed in 5.1.6](https://github.com/eclipse/rdf4j/milestone/122?closed=1). + +### Acknowledgements + +This release was made possible by contributions from [Jerven Bolleman](https://github.com/JervenBolleman), [Piotr Sowiński](https://github.com/Ostrzyciel), [Håvard M. Ottestad](https://github.com/hmottestad) and [kenwenzel](https://github.com/kenwenzel). diff --git a/site/static/javadoc/5.1.6.tgz b/site/static/javadoc/5.1.6.tgz new file mode 100644 index 00000000000..74da9144111 Binary files /dev/null and b/site/static/javadoc/5.1.6.tgz differ diff --git a/site/static/javadoc/latest.tgz b/site/static/javadoc/latest.tgz index 272f9265a7d..74da9144111 100644 Binary files a/site/static/javadoc/latest.tgz and b/site/static/javadoc/latest.tgz differ diff --git a/spring-components/pom.xml b/spring-components/pom.xml index 5f1d0e1e819..ab3dd692278 100644 --- a/spring-components/pom.xml +++ b/spring-components/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT pom diff --git a/spring-components/rdf4j-spring-demo/pom.xml b/spring-components/rdf4j-spring-demo/pom.xml index 04a75697525..5eb03e96408 100644 --- a/spring-components/rdf4j-spring-demo/pom.xml +++ b/spring-components/rdf4j-spring-demo/pom.xml @@ -7,7 +7,7 @@ org.eclipse.rdf4j rdf4j-spring-components - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT diff --git a/spring-components/rdf4j-spring/pom.xml b/spring-components/rdf4j-spring/pom.xml index 41e689e371f..3aa7e9b9530 100644 --- a/spring-components/rdf4j-spring/pom.xml +++ b/spring-components/rdf4j-spring/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-spring-components - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-spring RDF4J: Spring diff --git a/spring-components/spring-boot-sparql-web/pom.xml b/spring-components/spring-boot-sparql-web/pom.xml index 1f50d723fa7..fe1edfafdb6 100644 --- a/spring-components/spring-boot-sparql-web/pom.xml +++ b/spring-components/spring-boot-sparql-web/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-spring-components - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-spring-boot-sparql-web RDF4J: Spring boot component for a HTTP sparql server diff --git a/testsuites/benchmark/pom.xml b/testsuites/benchmark/pom.xml index c2f1db44769..c748380063f 100644 --- a/testsuites/benchmark/pom.xml +++ b/testsuites/benchmark/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-benchmark RDF4J: benchmarks diff --git a/testsuites/geosparql/pom.xml b/testsuites/geosparql/pom.xml index 681e7cc523e..6c030ec52ca 100644 --- a/testsuites/geosparql/pom.xml +++ b/testsuites/geosparql/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-geosparql-testsuite RDF4J: GeoSPARQL compliance test suite diff --git a/testsuites/lucene/pom.xml b/testsuites/lucene/pom.xml index 18360ad4420..f29e463e89f 100644 --- a/testsuites/lucene/pom.xml +++ b/testsuites/lucene/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-lucene-testsuite RDF4J: Lucene Sail Tests diff --git a/testsuites/model/pom.xml b/testsuites/model/pom.xml index 70234335ef5..39ea1c378b0 100644 --- a/testsuites/model/pom.xml +++ b/testsuites/model/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-model-testsuite RDF4J: Model API testsuite diff --git a/testsuites/pom.xml b/testsuites/pom.xml index 30f9014d584..b6762c4bdf1 100644 --- a/testsuites/pom.xml +++ b/testsuites/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-testsuites pom diff --git a/testsuites/queryresultio/pom.xml b/testsuites/queryresultio/pom.xml index d6c456e40c3..bb9e5ee6f5f 100644 --- a/testsuites/queryresultio/pom.xml +++ b/testsuites/queryresultio/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-queryresultio-testsuite RDF4J: QueryResultIO testsuite diff --git a/testsuites/repository/pom.xml b/testsuites/repository/pom.xml index bb811406609..e748a245120 100644 --- a/testsuites/repository/pom.xml +++ b/testsuites/repository/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-repository-testsuite RDF4J: Repository API testsuite diff --git a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/RepositoryConnectionTest.java b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/RepositoryConnectionTest.java index a22e98b9f73..533d5488a24 100644 --- a/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/RepositoryConnectionTest.java +++ b/testsuites/repository/src/main/java/org/eclipse/rdf4j/testsuite/repository/RepositoryConnectionTest.java @@ -1441,6 +1441,36 @@ public void testEmptyRollback(IsolationLevel level) { assertThat(testCon2.isEmpty()).isTrue(); } + @ParameterizedTest + @MethodSource("parameters") + public void testRollbackAfterInterrupt(IsolationLevel level) { + setupTest(level); + + if (IsolationLevels.NONE.isCompatibleWith(level)) { + return; + } + + testCon.begin(); + testCon.add(bob, name, nameBob); + assertThat(testCon.hasStatement(bob, name, nameBob, true)).isTrue(); + + boolean wasInterrupted = Thread.currentThread().isInterrupted(); + try { + Thread.currentThread().interrupt(); + assertThat(Thread.currentThread().isInterrupted()).isTrue(); + + testCon.rollback(); + } finally { + if (!wasInterrupted) { + Thread.interrupted(); + } + } + + assertThat(testCon.hasStatement(bob, name, nameBob, true)).isFalse(); + assertThat(testCon.isEmpty()).isTrue(); + assertThat(testCon2.isEmpty()).isTrue(); + } + @ParameterizedTest @MethodSource("parameters") public void testEmptyCommit(IsolationLevel level) { diff --git a/testsuites/rio/pom.xml b/testsuites/rio/pom.xml index ac9dcf9dc9e..46983abcf68 100644 --- a/testsuites/rio/pom.xml +++ b/testsuites/rio/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-rio-testsuite RDF4J: Rio compliance test suite diff --git a/testsuites/sail/pom.xml b/testsuites/sail/pom.xml index 85da81f401a..318585f8d14 100644 --- a/testsuites/sail/pom.xml +++ b/testsuites/sail/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sail-testsuite RDF4J: Sail API testsuite diff --git a/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/RDFStoreTest.java b/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/RDFStoreTest.java index 47549df7572..12a7ea95b9f 100644 --- a/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/RDFStoreTest.java +++ b/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/RDFStoreTest.java @@ -584,7 +584,7 @@ public void testClose() { } catch (IllegalStateException e) { // do nothing, this is expected } catch (SailException e) { - fail(e.getMessage()); + throw e; } } diff --git a/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/SailConcurrencyTest.java b/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/SailConcurrencyTest.java index 4ee406b0e7f..14db4a52540 100644 --- a/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/SailConcurrencyTest.java +++ b/testsuites/sail/src/main/java/org/eclipse/rdf4j/testsuite/sail/SailConcurrencyTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; import org.slf4j.Logger; @@ -320,6 +321,7 @@ public void testGetContextIDs() throws Exception { @Test @Timeout(value = 30, unit = TimeUnit.MINUTES) public void testConcurrentConnectionsShutdown() throws InterruptedException { + System.err.println("Running testConcurrentConnectionsShutdown"); if (store instanceof AbstractSail) { ((AbstractSail) store).setConnectionTimeOut(200); } else if (store instanceof SailWrapper) { @@ -365,6 +367,7 @@ public void testConcurrentConnectionsShutdown() throws InterruptedException { @Test @Timeout(value = 30, unit = TimeUnit.MINUTES) public void testSerialThreads() throws InterruptedException { + System.err.println("Running testSerialThreads"); if (store instanceof AbstractSail) { ((AbstractSail) store).setConnectionTimeOut(200); } else if (store instanceof SailWrapper) { @@ -446,6 +449,7 @@ public void testSerialThreads() throws InterruptedException { @Test @Timeout(value = 30, unit = TimeUnit.MINUTES) public void testConcurrentConnectionsShutdownReadCommitted() throws InterruptedException { + System.err.println("Running testConcurrentConnectionsShutdownReadCommitted"); if (store instanceof AbstractSail) { ((AbstractSail) store).setConnectionTimeOut(200); } else if (store instanceof SailWrapper) { @@ -499,9 +503,11 @@ public void testConcurrentConnectionsShutdownReadCommitted() throws InterruptedE } - @Test +// @Test + @RepeatedTest(5) @Timeout(value = 30, unit = TimeUnit.MINUTES) public void testConcurrentConnectionsShutdownAndClose() throws InterruptedException { + System.err.println("Running testConcurrentConnectionsShutdownAndClose"); if (store instanceof AbstractSail) { ((AbstractSail) store).setConnectionTimeOut(200); } @@ -522,8 +528,6 @@ public void testConcurrentConnectionsShutdownAndClose() throws InterruptedExcept connection1.get().begin(IsolationLevels.NONE); connection1.get().clear(); }); - thread1.setName("Thread 1"); - thread1.start(); CountDownLatch countDownLatch2 = new CountDownLatch(1); Thread thread2 = new Thread(() -> { @@ -534,6 +538,8 @@ public void testConcurrentConnectionsShutdownAndClose() throws InterruptedExcept }); thread2.setName("Thread 2"); + thread1.setName("Thread 1"); + thread1.start(); thread2.start(); countDownLatch1.await(); @@ -578,6 +584,7 @@ public void testConcurrentConnectionsShutdownAndClose() throws InterruptedExcept @Test @Timeout(value = 30, unit = TimeUnit.MINUTES) public void testConcurrentConnectionsShutdownAndCloseRollback() throws InterruptedException { + System.err.println("Running testConcurrentConnectionsShutdownAndCloseRollback"); if (store instanceof AbstractSail) { ((AbstractSail) store).setConnectionTimeOut(200); } diff --git a/testsuites/sparql/pom.xml b/testsuites/sparql/pom.xml index 4f133b5d562..2ff05ed717c 100644 --- a/testsuites/sparql/pom.xml +++ b/testsuites/sparql/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-testsuites - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-sparql-testsuite RDF4J: SPARQL compliance test suite diff --git a/tools/config/pom.xml b/tools/config/pom.xml index e25367db25b..592a7f968fd 100644 --- a/tools/config/pom.xml +++ b/tools/config/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-config RDF4J: application configuration diff --git a/tools/console/pom.xml b/tools/console/pom.xml index 6a88f40b2e4..615bee26c98 100644 --- a/tools/console/pom.xml +++ b/tools/console/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-console RDF4J: Console diff --git a/tools/federation/pom.xml b/tools/federation/pom.xml index f050926836b..e56caa6ab06 100644 --- a/tools/federation/pom.xml +++ b/tools/federation/pom.xml @@ -8,7 +8,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT diff --git a/tools/pom.xml b/tools/pom.xml index 2f03e003064..f5f3a0a1d8f 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-tools pom diff --git a/tools/runtime-osgi/pom.xml b/tools/runtime-osgi/pom.xml index 0209c7f0568..f20f59df869 100644 --- a/tools/runtime-osgi/pom.xml +++ b/tools/runtime-osgi/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-runtime-osgi bundle diff --git a/tools/runtime/pom.xml b/tools/runtime/pom.xml index 02adbf3cdbb..dc30259dc7e 100644 --- a/tools/runtime/pom.xml +++ b/tools/runtime/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-runtime RDF4J: Runtime diff --git a/tools/server-spring/pom.xml b/tools/server-spring/pom.xml index 7e4c9364af6..551895277b5 100644 --- a/tools/server-spring/pom.xml +++ b/tools/server-spring/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-http-server-spring RDF4J: HTTP server - core diff --git a/tools/server/pom.xml b/tools/server/pom.xml index 21599a6c105..598004804b0 100644 --- a/tools/server/pom.xml +++ b/tools/server/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-http-server war diff --git a/tools/workbench/pom.xml b/tools/workbench/pom.xml index 5117672f2db..b0bb86a7c5d 100644 --- a/tools/workbench/pom.xml +++ b/tools/workbench/pom.xml @@ -4,7 +4,7 @@ org.eclipse.rdf4j rdf4j-tools - 5.1.6-SNAPSHOT + 5.1.7-SNAPSHOT rdf4j-http-workbench war