This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/main by this push:
     new 019db539c1 JENA-2319: Fix concurrency issue with ConfigurableAnalyzer
     new 60845e081d Merge pull request #1247 from epimorphics/fix/jena-2319
019db539c1 is described below

commit 019db539c17b61705af4f9399ef40e9a0ee38ec6
Author: der <[email protected]>
AuthorDate: Fri Apr 1 14:01:08 2022 +0100

    JENA-2319: Fix concurrency issue with ConfigurableAnalyzer
    
    Text query parsing when using a ConfigurableAnalyzer is not
    thread safe. Underlying issue may be with Lucene analyzers since
    ConfigurableAnalyzer itself looks clean.
    
    Proposed fix is just a brute force synchronization, since without a
    clearer chracterisation it is hard detect all possibly unsafe
    analyzer configurations
---
 .../apache/jena/query/text/TextIndexLucene.java    | 15 ++--
 ...DatasetConcurrencyWithConfigurableAnalyzer.java | 89 ++++++++++++++++++++++
 2 files changed, 98 insertions(+), 6 deletions(-)

diff --git 
a/jena-text/src/main/java/org/apache/jena/query/text/TextIndexLucene.java 
b/jena-text/src/main/java/org/apache/jena/query/text/TextIndexLucene.java
index 78fdc83567..ba5274b774 100644
--- a/jena-text/src/main/java/org/apache/jena/query/text/TextIndexLucene.java
+++ b/jena-text/src/main/java/org/apache/jena/query/text/TextIndexLucene.java
@@ -33,10 +33,7 @@ import org.apache.jena.datatypes.TypeMapper ;
 import org.apache.jena.datatypes.xsd.XSDDatatype ;
 import org.apache.jena.graph.Node ;
 import org.apache.jena.graph.NodeFactory ;
-import org.apache.jena.query.text.analyzer.IndexingMultilingualAnalyzer;
-import org.apache.jena.query.text.analyzer.MultilingualAnalyzer;
-import org.apache.jena.query.text.analyzer.QueryMultilingualAnalyzer;
-import org.apache.jena.query.text.analyzer.Util;
+import org.apache.jena.query.text.analyzer.*;
 import org.apache.jena.rdf.model.Resource;
 import org.apache.jena.rdf.model.ResourceFactory;
 import org.apache.jena.sparql.core.Var;
@@ -438,8 +435,14 @@ public class TextIndexLucene implements TextIndex {
             qp = new QueryParser(docDef.getPrimaryField(), analyzer);
         }
         qp.setAllowLeadingWildcard(true);
-        query = qp.parse(queryString);
-        return query ;
+
+        // Some analyzers are not thread safe, at least when used via 
ConfigurableAnalyzer
+        // Could be more selective here about when to synchronize but the 
suspect analyzer
+        // can appear at several places in the wrapped nest of analyzers so 
being conservative
+        synchronized (this) {
+            query = qp.parse(queryString);
+            return query;
+        }
     }
 
     private List<Map<String, Node>> get$(IndexReader indexReader, String uri) 
throws ParseException, IOException {
diff --git 
a/jena-text/src/test/java/org/apache/jena/query/text/TestDatasetConcurrencyWithConfigurableAnalyzer.java
 
b/jena-text/src/test/java/org/apache/jena/query/text/TestDatasetConcurrencyWithConfigurableAnalyzer.java
new file mode 100644
index 0000000000..f1a5075e5d
--- /dev/null
+++ 
b/jena-text/src/test/java/org/apache/jena/query/text/TestDatasetConcurrencyWithConfigurableAnalyzer.java
@@ -0,0 +1,89 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.query.text;
+
+import org.apache.jena.atlas.lib.StrUtils;
+import org.apache.jena.ext.com.google.common.collect.Sets;
+import org.apache.jena.query.ReadWrite;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.vocabulary.RDFS;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+
+public class TestDatasetConcurrencyWithConfigurableAnalyzer extends 
AbstractTestDatasetWithAnalyzer {
+    @Override
+    @Before
+    public void before () {
+        init(StrUtils.strjoinNL(
+                "text:ConfigurableAnalyzer ;",
+                "text:tokenizer text:WhitespaceTokenizer ;",
+                "text:filters (text:ASCIIFoldingFilter text:LowerCaseFilter)"
+        ));
+        initDataset();
+    }
+
+    private void initDataset() {
+        Model model = dataset.getDefaultModel();
+        dataset.begin(ReadWrite.WRITE);
+        for (int i = 0; i < 1000; i++) {
+            String token = "Foo" + i;
+            model.createResource(RESOURCE_BASE + 
token).addProperty(RDFS.label, token);
+        }
+        dataset.commit();
+    }
+
+    private boolean testOneQuery(int probe) {
+        final String testName = "testConfigurableAnalyzerIsConcurrencySafe" + 
probe;
+        String query = QUERY_PROLOG + "select ?s WHERE {?s text:query 
(rdfs:label 'foo" + probe + "' 10).}";
+        try {
+            doTestQuery(dataset, testName, query, 
Sets.newHashSet(RESOURCE_BASE + "Foo" + probe), 1);
+        } catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+    @Test
+    public void testConfigurableAnalyzerIsConcurrencySafe () {
+        final int parallelism = 8;
+        ExecutorService executorService = 
Executors.newFixedThreadPool(parallelism);
+        try {
+            for (int i = 0; i < 20; i++) {
+                Future<Boolean>[] results = (Future<Boolean>[]) new 
Future<?>[parallelism];
+                for (int j = 0; j < parallelism; j++) {
+                    final int probe = i;
+                    results[j] = executorService.submit(() -> 
testOneQuery(probe));
+                }
+                for (int j = 0; j < parallelism; j++) {
+                    Assert.assertTrue("Probe " + i + " failed", 
results[j].get());
+                }
+            }
+        } catch (InterruptedException e) {
+            // exit silently on interrupt
+        } catch (ExecutionException e) {
+            Assert.assertTrue("Concurrency exception: " + e.getMessage(), 
false);
+        }
+    }
+}

Reply via email to