Skip to content

Commit 791be95

Browse files
committed
Merge branch 'spring-projects/2.1.x'
Closes gh-17018
2 parents 9d6695a + 8e26898 commit 791be95

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySource.java

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.Collections;
21+
import java.util.ConcurrentModificationException;
2122
import java.util.HashSet;
2223
import java.util.Iterator;
2324
import java.util.List;
@@ -44,8 +45,6 @@
4445
class SpringIterableConfigurationPropertySource extends SpringConfigurationPropertySource
4546
implements IterableConfigurationPropertySource {
4647

47-
private volatile Object cacheKey;
48-
4948
private volatile Cache cache;
5049

5150
SpringIterableConfigurationPropertySource(EnumerablePropertySource<?> propertySource,
@@ -131,13 +130,23 @@ private PropertyMapping[] getPropertyMappings(Cache cache) {
131130
}
132131

133132
private Cache getCache() {
134-
CacheKey cacheKey = CacheKey.get(getPropertySource());
135-
if (ObjectUtils.nullSafeEquals(cacheKey, this.cacheKey)) {
136-
return this.cache;
133+
CacheKey key = CacheKey.get(getPropertySource());
134+
if (key == null) {
135+
return null;
136+
}
137+
Cache cache = this.cache;
138+
try {
139+
if (cache != null && cache.hasKeyEqualTo(key)) {
140+
return cache;
141+
}
142+
cache = new Cache(key.copy());
143+
this.cache = cache;
144+
return cache;
145+
}
146+
catch (ConcurrentModificationException ex) {
147+
// Not fatal at this point, we can continue without a cache
148+
return null;
137149
}
138-
this.cache = new Cache();
139-
this.cacheKey = cacheKey.copy();
140-
return this.cache;
141150
}
142151

143152
@Override
@@ -147,10 +156,20 @@ protected EnumerablePropertySource<?> getPropertySource() {
147156

148157
private static class Cache {
149158

159+
private final CacheKey key;
160+
150161
private List<ConfigurationPropertyName> names;
151162

152163
private PropertyMapping[] mappings;
153164

165+
Cache(CacheKey key) {
166+
this.key = key;
167+
}
168+
169+
public boolean hasKeyEqualTo(CacheKey key) {
170+
return this.key.equals(key);
171+
}
172+
154173
public List<ConfigurationPropertyName> getNames() {
155174
return this.names;
156175
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/source/SpringIterableConfigurationPropertySourceTests.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616

1717
package org.springframework.boot.context.properties.source;
1818

19+
import java.util.ConcurrentModificationException;
20+
import java.util.Iterator;
1921
import java.util.LinkedHashMap;
22+
import java.util.LinkedHashSet;
2023
import java.util.Map;
24+
import java.util.Set;
2125

2226
import org.junit.Test;
2327

@@ -169,6 +173,21 @@ public void propertySourceKeyDataChangeInvalidatesCache() {
169173
assertThat(adapter.stream().count()).isEqualTo(3);
170174
}
171175

176+
@Test
177+
public void concurrentModificationExceptionInvalidatesCache() {
178+
// gh-17013
179+
ConcurrentModificationThrowingMap<String, Object> map = new ConcurrentModificationThrowingMap<>();
180+
map.put("key1", "value1");
181+
map.put("key2", "value2");
182+
EnumerablePropertySource<?> source = new MapPropertySource("test", map);
183+
SpringIterableConfigurationPropertySource adapter = new SpringIterableConfigurationPropertySource(
184+
source, DefaultPropertyMapper.INSTANCE);
185+
assertThat(adapter.stream().count()).isEqualTo(2);
186+
map.setThrowException(true);
187+
map.put("key3", "value3");
188+
assertThat(adapter.stream().count()).isEqualTo(3);
189+
}
190+
172191
/**
173192
* Test {@link PropertySource} that's also an {@link OriginLookup}.
174193
*/
@@ -206,4 +225,37 @@ public String toString() {
206225

207226
}
208227

228+
private static class ConcurrentModificationThrowingMap<K, V>
229+
extends LinkedHashMap<K, V> {
230+
231+
private boolean throwException;
232+
233+
public void setThrowException(boolean throwException) {
234+
this.throwException = throwException;
235+
}
236+
237+
@Override
238+
public Set<K> keySet() {
239+
return new KeySet(super.keySet());
240+
}
241+
242+
private class KeySet extends LinkedHashSet<K> {
243+
244+
KeySet(Set<K> keySet) {
245+
super(keySet);
246+
}
247+
248+
@Override
249+
public Iterator<K> iterator() {
250+
if (ConcurrentModificationThrowingMap.this.throwException) {
251+
ConcurrentModificationThrowingMap.this.throwException = false;
252+
throw new ConcurrentModificationException();
253+
}
254+
return super.iterator();
255+
}
256+
257+
}
258+
259+
}
260+
209261
}

0 commit comments

Comments
 (0)