Skip to content

Commit d010cad

Browse files
jkiang13ywelsch
authored andcommitted
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
1 parent 2eafe83 commit d010cad

File tree

2 files changed

+29
-3
lines changed

2 files changed

+29
-3
lines changed

core/src/main/java/org/elasticsearch/index/mapper/MapperService.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ public enum MergeReason {
110110
private volatile Map<String, DocumentMapper> mappers = emptyMap();
111111

112112
private volatile FieldTypeLookup fieldTypes;
113-
private volatile Map<String, ObjectMapper> fullPathObjectMappers = new HashMap<>();
113+
private volatile Map<String, ObjectMapper> fullPathObjectMappers = emptyMap();
114114
private boolean hasNested = false; // updated dynamically to true when a nested object is added
115115
private boolean allEnabled = false; // updated dynamically to true when _all is enabled
116116

@@ -394,6 +394,7 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
394394

395395
for (ObjectMapper objectMapper : objectMappers) {
396396
if (fullPathObjectMappers == this.fullPathObjectMappers) {
397+
// first time through the loops
397398
fullPathObjectMappers = new HashMap<>(this.fullPathObjectMappers);
398399
}
399400
fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper);
@@ -414,6 +415,7 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
414415

415416
if (oldMapper == null && newMapper.parentFieldMapper().active()) {
416417
if (parentTypes == this.parentTypes) {
418+
// first time through the loop
417419
parentTypes = new HashSet<>(this.parentTypes);
418420
}
419421
parentTypes.add(mapper.parentFieldMapper().type());
@@ -456,8 +458,15 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
456458
// make structures immutable
457459
mappers = Collections.unmodifiableMap(mappers);
458460
results = Collections.unmodifiableMap(results);
459-
parentTypes = Collections.unmodifiableSet(parentTypes);
460-
fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);
461+
462+
// only need to immutably rewrap these if the previous reference was changed.
463+
// if not then they are already implicitly immutable.
464+
if (fullPathObjectMappers != this.fullPathObjectMappers) {
465+
fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);
466+
}
467+
if (parentTypes != this.parentTypes) {
468+
parentTypes = Collections.unmodifiableSet(parentTypes);
469+
}
461470

462471
// commit the change
463472
if (defaultMappingSource != null) {

core/src/test/java/org/elasticsearch/index/mapper/MapperServiceTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.HashMap;
3939
import java.util.HashSet;
4040
import java.util.Map;
41+
import java.util.Set;
4142
import java.util.concurrent.ExecutionException;
4243
import java.util.function.Function;
4344

@@ -189,6 +190,22 @@ public void testMergeWithMap() throws Throwable {
189190
assertThat(e.getMessage(), startsWith("Failed to parse mapping [type1]: "));
190191
}
191192

193+
public void testMergeParentTypesSame() {
194+
// Verifies that a merge (absent a DocumentMapper change)
195+
// doesn't change the parentTypes reference.
196+
// The collection was being rewrapped with each merge
197+
// in v5.2 resulting in eventual StackOverflowErrors.
198+
// https://github.com/elastic/elasticsearch/issues/23604
199+
200+
IndexService indexService1 = createIndex("index1");
201+
MapperService mapperService = indexService1.mapperService();
202+
Set<String> parentTypes = mapperService.getParentTypes();
203+
204+
Map<String, Map<String, Object>> mappings = new HashMap<>();
205+
mapperService.merge(mappings, MergeReason.MAPPING_UPDATE, false);
206+
assertSame(parentTypes, mapperService.getParentTypes());
207+
}
208+
192209
public void testOtherDocumentMappersOnlyUpdatedWhenChangingFieldType() throws IOException {
193210
IndexService indexService = createIndex("test");
194211

0 commit comments

Comments
 (0)