Skip to content

Commit 74fc290

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 10cb37d commit 74fc290

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

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

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

392392
for (ObjectMapper objectMapper : objectMappers) {
393393
if (fullPathObjectMappers == this.fullPathObjectMappers) {
394+
// first time through the loops
394395
fullPathObjectMappers = new HashMap<>(this.fullPathObjectMappers);
395396
}
396397
fullPathObjectMappers.put(objectMapper.fullPath(), objectMapper);
@@ -411,6 +412,7 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
411412

412413
if (oldMapper == null && newMapper.parentFieldMapper().active()) {
413414
if (parentTypes == this.parentTypes) {
415+
// first time through the loop
414416
parentTypes = new HashSet<>(this.parentTypes);
415417
}
416418
parentTypes.add(mapper.parentFieldMapper().type());
@@ -453,8 +455,15 @@ private synchronized Map<String, DocumentMapper> internalMerge(@Nullable Documen
453455
// make structures immutable
454456
mappers = Collections.unmodifiableMap(mappers);
455457
results = Collections.unmodifiableMap(results);
456-
parentTypes = Collections.unmodifiableSet(parentTypes);
457-
fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);
458+
459+
// only need to immutably rewrap these if the previous reference was changed.
460+
// if not then they are already implicitly immutable.
461+
if (fullPathObjectMappers != this.fullPathObjectMappers) {
462+
fullPathObjectMappers = Collections.unmodifiableMap(fullPathObjectMappers);
463+
}
464+
if (parentTypes != this.parentTypes) {
465+
parentTypes = Collections.unmodifiableSet(parentTypes);
466+
}
458467

459468
// commit the change
460469
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
@@ -36,6 +36,7 @@
3636
import java.util.HashMap;
3737
import java.util.HashSet;
3838
import java.util.Map;
39+
import java.util.Set;
3940
import java.util.concurrent.ExecutionException;
4041
import java.util.function.Function;
4142

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

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

0 commit comments

Comments
 (0)