Skip to content

Commit 510e474

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 468813e commit 510e474

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
@@ -115,7 +115,7 @@ public enum MergeReason {
115115
private volatile Map<String, DocumentMapper> mappers = emptyMap();
116116

117117
private volatile FieldTypeLookup fieldTypes;
118-
private volatile Map<String, ObjectMapper> fullPathObjectMappers = new HashMap<>();
118+
private volatile Map<String, ObjectMapper> fullPathObjectMappers = emptyMap();
119119
private boolean hasNested = false; // updated dynamically to true when a nested object is added
120120
private boolean allEnabled = false; // updated dynamically to true when _all is enabled
121121

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

400400
for (ObjectMapper objectMapper : objectMappers) {
401401
if (fullPathObjectMappers == this.fullPathObjectMappers) {
402+
// first time through the loops
402403
fullPathObjectMappers = new HashMap<>(this.fullPathObjectMappers);
403404
}
404405
fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper);
@@ -419,6 +420,7 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
419420

420421
if (oldMapper == null && newMapper.parentFieldMapper().active()) {
421422
if (parentTypes == this.parentTypes) {
423+
// first time through the loop
422424
parentTypes = new HashSet<>(this.parentTypes);
423425
}
424426
parentTypes.add(mapper.parentFieldMapper().type());
@@ -461,8 +463,15 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
461463
// make structures immutable
462464
mappers = Collections.unmodifiableMap(mappers);
463465
results = Collections.unmodifiableMap(results);
464-
parentTypes = Collections.unmodifiableSet(parentTypes);
465-
fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);
466+
467+
// only need to immutably rewrap these if the previous reference was changed.
468+
// if not then they are already implicitly immutable.
469+
if (fullPathObjectMappers != this.fullPathObjectMappers) {
470+
fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);
471+
}
472+
if (parentTypes != this.parentTypes) {
473+
parentTypes = Collections.unmodifiableSet(parentTypes);
474+
}
466475

467476
// commit the change
468477
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

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

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

0 commit comments

Comments
 (0)