You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ma...@apache.org on 2020/08/22 18:42:21 UTC

[lucene-solr] 02/22: @582 Keep making Schema threadsafe.

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

markrmiller pushed a commit to branch reference_impl
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git

commit f19e31cecd4fecbec3193a877c8b125fca673982
Author: markrmiller@gmail.com <ma...@gmail.com>
AuthorDate: Thu Aug 20 11:51:46 2020 -0500

    @582 Keep making Schema threadsafe.
---
 .../handler/component/SpellCheckComponent.java     |  6 +-
 .../java/org/apache/solr/schema/IndexSchema.java   | 73 +++++++++++++---------
 .../org/apache/solr/schema/ManagedIndexSchema.java | 19 +++---
 .../org/apache/solr/spelling/SolrSpellChecker.java |  2 +-
 4 files changed, 60 insertions(+), 40 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java b/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
index 3599718..c301826 100644
--- a/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
+++ b/solr/core/src/java/org/apache/solr/handler/component/SpellCheckComponent.java
@@ -754,7 +754,11 @@ public class SpellCheckComponent extends SearchComponent implements SolrCoreAwar
         queryConverter = queryConverters.values().iterator().next();
         IndexSchema schema = core.getLatestSchema();
         String fieldTypeName = (String) initParams.get("queryAnalyzerFieldType");
-        FieldType fieldType = schema.getFieldTypes().get(fieldTypeName);
+
+        FieldType fieldType = null;
+        if (fieldTypeName != null) {
+          fieldType = schema.getFieldTypes().get(fieldTypeName);
+        }
         Analyzer analyzer = fieldType == null ? new WhitespaceAnalyzer()
                 : fieldType.getQueryAnalyzer();
         //TODO: There's got to be a better way!  Where's Spring when you need it?
diff --git a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
index 935b2c8..2691239 100644
--- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java
@@ -140,12 +140,14 @@ public class IndexSchema {
   private static XPathExpression defaultSearchFieldExp;
   private static XPathExpression solrQueryParserDefaultOpExp;
   private static XPathExpression schemaUniqueKeyExp;
-
-
+  private static XPathExpression fieldTypeXPathExpressionsExp;
+  private static XPathExpression copyFieldsExp;
 
   static String schemaNamePath = stepsToPath(SCHEMA, AT + NAME);
   static String schemaVersionPath = "/schema/@version";
 
+  static String copyFieldPath = "//" + COPY_FIELD;
+
   static String fieldTypeXPathExpressions = getFieldTypeXPathExpressions();
 
   static String  schemaSimPath = stepsToPath(SCHEMA, SIMILARITY); //   /schema/similarity
@@ -202,47 +204,59 @@ public class IndexSchema {
     } catch (XPathExpressionException e) {
       log.error("", e);
     }
+    try {
+      fieldTypeXPathExpressionsExp = xpath.compile(fieldTypeXPathExpressions);
+    } catch (XPathExpressionException e) {
+      log.error("", e);
+    }
+
+    try {
+      copyFieldsExp = xpath.compile(copyFieldPath);
+    } catch (XPathExpressionException e) {
+      log.error("", e);
+    }
+
   }
 
 
-  protected String resourceName;
-  protected String name;
+  protected volatile String resourceName;
+  protected volatile String name;
   protected final Version luceneVersion;
   protected float version;
   protected final SolrResourceLoader loader;
   protected final Properties substitutableProperties;
 
   // some code will add fields after construction, needs to be thread safe
-  protected final Map<String,SchemaField> fields = new ConcurrentHashMap<>(128);
+  protected volatile Map<String,SchemaField> fields = new ConcurrentHashMap<>(128);
 
-  protected Map<String,FieldType> fieldTypes = new HashMap<>(64);
+  protected volatile Map<String,FieldType> fieldTypes = new ConcurrentHashMap<>(64);
 
-  protected List<SchemaField> fieldsWithDefaultValue = new ArrayList<>(64);
-  protected Collection<SchemaField> requiredFields = new HashSet<>(32);
-  protected DynamicField[] dynamicFields = new DynamicField[] {};
+  protected volatile Set<SchemaField> fieldsWithDefaultValue = ConcurrentHashMap.newKeySet(64);
+  protected volatile Collection<SchemaField> requiredFields = ConcurrentHashMap.newKeySet(32);
+  protected volatile DynamicField[] dynamicFields = new DynamicField[] {};
 
   public DynamicField[] getDynamicFields() { return dynamicFields; }
 
   protected final Cache<String, SchemaField> dynamicFieldCache = new ConcurrentLRUCache(10000, 8000, 9000,100, false,false, null);
 
-  private Analyzer indexAnalyzer;
-  private Analyzer queryAnalyzer;
+  private volatile Analyzer indexAnalyzer;
+  private volatile Analyzer queryAnalyzer;
 
-  protected List<SchemaAware> schemaAware = new ArrayList<>(64);
+  protected volatile Set<SchemaAware> schemaAware = ConcurrentHashMap.newKeySet(64);
 
-  protected Map<String,Set<CopyField>> copyFieldsMap = new ConcurrentHashMap<>(64);
+  protected volatile Map<String,Set<CopyField>> copyFieldsMap = new ConcurrentHashMap<>(64);
   public Map<String,Set<CopyField>> getCopyFieldsMap() { return Collections.unmodifiableMap(copyFieldsMap); }
 
-  protected DynamicCopy[] dynamicCopyFields = new DynamicCopy[] {};
+  protected volatile DynamicCopy[] dynamicCopyFields = new DynamicCopy[] {};
   public DynamicCopy[] getDynamicCopyFields() { return dynamicCopyFields; }
 
-  private Map<FieldType, PayloadDecoder> decoders = new ConcurrentHashMap<>();  // cache to avoid scanning token filters repeatedly, unnecessarily
+  private final Map<FieldType, PayloadDecoder> decoders = new ConcurrentHashMap<>();  // cache to avoid scanning token filters repeatedly, unnecessarily
 
   /**
    * keys are all fields copied to, count is num of copyField
    * directives that target them.
    */
-  protected Map<SchemaField, Integer> copyFieldTargetCounts = new HashMap<>();
+  protected volatile Map<SchemaField, Integer> copyFieldTargetCounts = new ConcurrentHashMap<>(32);
 
   /**
    * Constructs a schema using the specified resource name and stream.
@@ -341,7 +355,7 @@ public class IndexSchema {
   /**
    * Provides direct access to the List containing all fields with a default value
    */
-  public List<SchemaField> getFieldsWithDefaultValue() { return fieldsWithDefaultValue; }
+  public Set<SchemaField> getFieldsWithDefaultValue() { return fieldsWithDefaultValue; }
 
   /**
    * Provides direct access to the List containing all required fields.  This
@@ -499,15 +513,15 @@ public class IndexSchema {
   }
 
   private class SolrIndexAnalyzer extends DelegatingAnalyzerWrapper {
-    protected final HashMap<String, Analyzer> analyzers;
+    protected final Map<String, Analyzer> analyzers;
 
     SolrIndexAnalyzer() {
       super(PER_FIELD_REUSE_STRATEGY);
       analyzers = analyzerCache();
     }
 
-    protected HashMap<String, Analyzer> analyzerCache() {
-      HashMap<String, Analyzer> cache = new HashMap<>();
+    protected Map<String, Analyzer> analyzerCache() {
+      Map<String, Analyzer> cache = new ConcurrentHashMap<>();
       for (SchemaField f : getFields().values()) {
         Analyzer analyzer = f.getType().getIndexAnalyzer();
         cache.put(f.getName(), analyzer);
@@ -527,8 +541,8 @@ public class IndexSchema {
     SolrQueryAnalyzer() {}
 
     @Override
-    protected HashMap<String, Analyzer> analyzerCache() {
-      HashMap<String, Analyzer> cache = new HashMap<>();
+    protected Map<String, Analyzer> analyzerCache() {
+      Map<String, Analyzer> cache = new ConcurrentHashMap<>();
        for (SchemaField f : getFields().values()) {
         Analyzer analyzer = f.getType().getQueryAnalyzer();
         cache.put(f.getName(), analyzer);
@@ -554,7 +568,6 @@ public class IndexSchema {
       // in the current case though, the stream is valid so we wont load the resource by name
       XmlConfigFile schemaConf = new XmlConfigFile(loader, SCHEMA, is, SLASH+SCHEMA+SLASH, substitutableProperties);
       Document document = schemaConf.getDocument();
-      final XPath xpath = XmlConfigFile.getXpath();
 
       Node nd = (Node) schemaNameExp.evaluate(document, XPathConstants.NODE);
       StringBuilder sb = new StringBuilder();
@@ -585,11 +598,11 @@ public class IndexSchema {
       // load the Field Types
       final FieldTypePluginLoader typeLoader = new FieldTypePluginLoader(this, fieldTypes, schemaAware);
 
-      NodeList nodes = (NodeList) xpath.evaluate(fieldTypeXPathExpressions, document, XPathConstants.NODESET);
+      NodeList nodes = (NodeList) fieldTypeXPathExpressionsExp.evaluate(document, XPathConstants.NODESET);
       typeLoader.load(loader, nodes);
 
       // load the fields
-      Map<String,Boolean> explicitRequiredProp = loadFields(document, xpath);
+      Map<String,Boolean> explicitRequiredProp = loadFields(document);
 
 
       Node node = (Node) schemaSimExp.evaluate(document, XPathConstants.NODE);
@@ -687,7 +700,7 @@ public class IndexSchema {
       // expression = "/schema/copyField";
     
       dynamicCopyFields = new DynamicCopy[] {};
-      loadCopyFields(document, xpath);
+      loadCopyFields(document);
 
       postReadInform();
 
@@ -722,7 +735,7 @@ public class IndexSchema {
    * 
    * @return a map from field name to explicit required value  
    */ 
-  protected synchronized Map<String,Boolean> loadFields(Document document, XPath xpath) throws XPathExpressionException {
+  protected synchronized Map<String,Boolean> loadFields(Document document) throws XPathExpressionException {
     // Hang on to the fields that say if they are required -- this lets us set a reasonable default for the unique key
     Map<String,Boolean> explicitRequiredProp = new HashMap<>();
     
@@ -745,7 +758,7 @@ public class IndexSchema {
       FieldType ft = fieldTypes.get(type);
       if (ft==null) {
         throw new SolrException
-            (ErrorCode.BAD_REQUEST, "Unknown " + FIELD_TYPE + " '" + type + "' specified on field " + name);
+            (ErrorCode.BAD_REQUEST, "Unknown " + FIELD_TYPE + " '" + type + "' specified on field " + name + " types:" + fieldTypes);
       }
 
       Map<String,String> args = DOMUtil.toMapExcept(attrs, NAME, TYPE);
@@ -813,9 +826,9 @@ public class IndexSchema {
   /**
    * Loads the copy fields
    */
-  protected synchronized void loadCopyFields(Document document, XPath xpath) throws XPathExpressionException {
+  protected synchronized void loadCopyFields(Document document) throws XPathExpressionException {
     String expression = "//" + COPY_FIELD;
-    NodeList nodes = (NodeList)xpath.evaluate(expression, document, XPathConstants.NODESET);
+    NodeList nodes = (NodeList)copyFieldsExp.evaluate(document, XPathConstants.NODESET);
 
     for (int i=0; i<nodes.getLength(); i++) {
       Node node = nodes.item(i);
diff --git a/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java b/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java
index 96be6fb..78c04a2 100644
--- a/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java
+++ b/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchema.java
@@ -398,6 +398,12 @@ public final class ManagedIndexSchema extends IndexSchema {
       }
       newSchema = shallowCopy(true);
 
+      newSchema.fields = new ConcurrentHashMap<>(fields);
+      newSchema.requiredFields = ConcurrentHashMap.newKeySet(requiredFields.size());
+      newSchema.requiredFields.addAll(requiredFields);
+      newSchema.fieldsWithDefaultValue = ConcurrentHashMap.newKeySet(fieldsWithDefaultValue.size());
+      newSchema.fieldsWithDefaultValue.addAll(fieldsWithDefaultValue);
+
       for (SchemaField newField : newFields) {
         if (null != newSchema.fields.get(newField.getName())) {
           String msg = "Field '" + newField.getName() + "' already exists.";
@@ -496,10 +502,10 @@ public final class ManagedIndexSchema extends IndexSchema {
       newSchema = shallowCopy(true);
       // clone data structures before modifying them
       newSchema.copyFieldsMap = cloneCopyFieldsMap(copyFieldsMap);
-      newSchema.copyFieldTargetCounts
-          = (Map<SchemaField,Integer>)((HashMap<SchemaField,Integer>)copyFieldTargetCounts).clone();
+      newSchema.copyFieldTargetCounts = new ConcurrentHashMap<>(copyFieldTargetCounts);
       newSchema.dynamicCopyFields = new DynamicCopy[dynamicCopyFields.length];
       System.arraycopy(dynamicCopyFields, 0, newSchema.dynamicCopyFields, 0, dynamicCopyFields.length);
+      newSchema.fields = new ConcurrentHashMap<>(fields);
 
       // Drop the old field
       newSchema.fields.remove(fieldName);
@@ -711,8 +717,7 @@ public final class ManagedIndexSchema extends IndexSchema {
       newSchema = shallowCopy(true);
 
       // clone data structures before modifying them
-      newSchema.copyFieldTargetCounts
-          = (Map<SchemaField,Integer>)((HashMap<SchemaField,Integer>)copyFieldTargetCounts).clone();
+      newSchema.copyFieldTargetCounts = new ConcurrentHashMap<>(copyFieldTargetCounts);
       newSchema.dynamicCopyFields = new DynamicCopy[dynamicCopyFields.length];
       System.arraycopy(dynamicCopyFields, 0, newSchema.dynamicCopyFields, 0, dynamicCopyFields.length);
 
@@ -955,9 +960,7 @@ public final class ManagedIndexSchema extends IndexSchema {
 
     // we shallow copied fieldTypes, but since we're changing them, we need to do a true
     // deep copy before adding the new field types
-    HashMap<String,FieldType> clone =
-        (HashMap<String,FieldType>)((HashMap<String,FieldType>)newSchema.fieldTypes).clone();
-    newSchema.fieldTypes = clone;
+    newSchema.fieldTypes = new ConcurrentHashMap<>(fieldTypes);
 
     // do a first pass to validate the field types don't exist already
     for (FieldType fieldType : fieldTypeList) {    
@@ -1399,7 +1402,7 @@ public final class ManagedIndexSchema extends IndexSchema {
       newSchema.requiredFields.addAll(requiredFields);
     }
 
-    // These don't need new collections - addFields() won't add members to them 
+    // These don't need new collections - addFields() won't add members to them
     newSchema.fieldTypes = fieldTypes;
     newSchema.dynamicFields = dynamicFields;
     newSchema.dynamicCopyFields = dynamicCopyFields;
diff --git a/solr/core/src/java/org/apache/solr/spelling/SolrSpellChecker.java b/solr/core/src/java/org/apache/solr/spelling/SolrSpellChecker.java
index 5543867..95079c5 100644
--- a/solr/core/src/java/org/apache/solr/spelling/SolrSpellChecker.java
+++ b/solr/core/src/java/org/apache/solr/spelling/SolrSpellChecker.java
@@ -67,7 +67,7 @@ public abstract class SolrSpellChecker {
       analyzer = schema.getFieldType(field).getQueryAnalyzer();
     }
     fieldTypeName = (String) config.get(FIELD_TYPE);
-    if (schema.getFieldTypes().containsKey(fieldTypeName))  {
+    if (fieldTypeName != null && schema.getFieldTypes().containsKey(fieldTypeName))  {
       FieldType fieldType = schema.getFieldTypes().get(fieldTypeName);
       analyzer = fieldType.getQueryAnalyzer();
     }