Fix MapperService StackOverflowError (#23605)

MapperService#parentTypes is rewrapped in an UnmodifiableSet in MapperService#internalMerge every time the cluster state is updated. After thousands of updates the collection is wrapped so deeply that calling a method on it generates a StackOverflowError.

Closes #23604
This commit is contained in:
Jordan Kiang 2017-03-20 03:53:35 -07:00 committed by Yannick Welsch
parent 2eafe8310e
commit d010cad503
2 changed files with 29 additions and 3 deletions

View File

@ -110,7 +110,7 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
private volatile Map<String, DocumentMapper> mappers = emptyMap(); private volatile Map<String, DocumentMapper> mappers = emptyMap();
private volatile FieldTypeLookup fieldTypes; private volatile FieldTypeLookup fieldTypes;
private volatile Map<String, ObjectMapper> fullPathObjectMappers = new HashMap<>(); private volatile Map<String, ObjectMapper> fullPathObjectMappers = emptyMap();
private boolean hasNested = false; // updated dynamically to true when a nested object is added private boolean hasNested = false; // updated dynamically to true when a nested object is added
private boolean allEnabled = false; // updated dynamically to true when _all is enabled private boolean allEnabled = false; // updated dynamically to true when _all is enabled
@ -394,6 +394,7 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
for (ObjectMapper objectMapper : objectMappers) { for (ObjectMapper objectMapper : objectMappers) {
if (fullPathObjectMappers == this.fullPathObjectMappers) { if (fullPathObjectMappers == this.fullPathObjectMappers) {
// first time through the loops
fullPathObjectMappers = new HashMap<>(this.fullPathObjectMappers); fullPathObjectMappers = new HashMap<>(this.fullPathObjectMappers);
} }
fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper); fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper);
@ -414,6 +415,7 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
if (oldMapper == null && newMapper.parentFieldMapper().active()) { if (oldMapper == null && newMapper.parentFieldMapper().active()) {
if (parentTypes == this.parentTypes) { if (parentTypes == this.parentTypes) {
// first time through the loop
parentTypes = new HashSet<>(this.parentTypes); parentTypes = new HashSet<>(this.parentTypes);
} }
parentTypes.add(mapper.parentFieldMapper().type()); parentTypes.add(mapper.parentFieldMapper().type());
@ -456,8 +458,15 @@ public class MapperService extends AbstractIndexComponent implements Closeable {
// make structures immutable // make structures immutable
mappers = Collections.unmodifiableMap(mappers); mappers = Collections.unmodifiableMap(mappers);
results = Collections.unmodifiableMap(results); results = Collections.unmodifiableMap(results);
parentTypes = Collections.unmodifiableSet(parentTypes);
// only need to immutably rewrap these if the previous reference was changed.
// if not then they are already implicitly immutable.
if (fullPathObjectMappers != this.fullPathObjectMappers) {
fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers); fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);
}
if (parentTypes != this.parentTypes) {
parentTypes = Collections.unmodifiableSet(parentTypes);
}
// commit the change // commit the change
if (defaultMappingSource != null) { if (defaultMappingSource != null) {

View File

@ -38,6 +38,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Function; import java.util.function.Function;
@ -189,6 +190,22 @@ public class MapperServiceTests extends ESSingleNodeTestCase {
assertThat(e.getMessage(), startsWith("Failed to parse mapping [type1]: ")); assertThat(e.getMessage(), startsWith("Failed to parse mapping [type1]: "));
} }
public void testMergeParentTypesSame() {
// Verifies that a merge (absent a DocumentMapper change)
// doesn't change the parentTypes reference.
// The collection was being rewrapped with each merge
// in v5.2 resulting in eventual StackOverflowErrors.
// https://github.com/elastic/elasticsearch/issues/23604
IndexService indexService1 = createIndex("index1");
MapperService mapperService = indexService1.mapperService();
Set<String> parentTypes = mapperService.getParentTypes();
Map<String, Map<String, Object>> mappings = new HashMap<>();
mapperService.merge(mappings, MergeReason.MAPPING_UPDATE, false);
assertSame(parentTypes, mapperService.getParentTypes());
}
public void testOtherDocumentMappersOnlyUpdatedWhenChangingFieldType() throws IOException { public void testOtherDocumentMappersOnlyUpdatedWhenChangingFieldType() throws IOException {
IndexService indexService = createIndex("test"); IndexService indexService = createIndex("test");