Skip to content

Commit 5c35529

Browse files
committed
Fix memory leak in CaffeineCacheManager when switching to static mode
When setCacheNames() is called to switch from dynamic to static mode, dynamically created caches were not removed from the internal cacheMap, causing: 1. Memory leak (orphaned cache references) 2. Violation of Javadoc contract stating cache names will be 'fixed' 3. getCacheNames() returning unexpected cache names This fix ensures that dynamically created caches are cleared when switching to static mode, while preserving custom caches registered via registerCustomCache(). Signed-off-by: Park Juhyeong <[email protected]> Signed-off-by: ParkJuhyeong <[email protected]>
1 parent 1260081 commit 5c35529

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCacheManager.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,16 @@ public CaffeineCacheManager(String... cacheNames) {
105105
* with no creation of further cache regions at runtime.
106106
* <p>Calling this with a {@code null} collection argument resets the
107107
* mode to 'dynamic', allowing for further creation of caches again.
108+
* <p><b>Note:</b> Switching to static mode will remove all dynamically created
109+
* caches, while preserving custom caches registered via
110+
* {@link #registerCustomCache(String, com.github.benmanes.caffeine.cache.Cache)}.
108111
*/
109112
public void setCacheNames(@Nullable Collection<String> cacheNames) {
110113
if (cacheNames != null) {
114+
// Remove all non-custom caches before setting up static caches
115+
this.cacheMap.keySet().retainAll(this.customCacheNames);
116+
117+
// Add the specified static caches
111118
for (String name : cacheNames) {
112119
this.cacheMap.put(name, createCaffeineCache(name));
113120
}

spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616

1717
package org.springframework.cache.caffeine;
1818

19+
import java.util.Arrays;
20+
import java.util.List;
1921
import java.util.concurrent.CompletableFuture;
2022

2123
import com.github.benmanes.caffeine.cache.CacheLoader;
2224
import com.github.benmanes.caffeine.cache.Caffeine;
2325
import com.github.benmanes.caffeine.cache.CaffeineSpec;
26+
import org.jspecify.annotations.Nullable;
2427
import org.junit.jupiter.api.Test;
2528

2629
import org.springframework.cache.Cache;
@@ -284,6 +287,68 @@ void setCacheNameNullRestoreDynamicMode() {
284287
assertThat(cm.getCache("someCache")).isNotNull();
285288
}
286289

290+
@Test
291+
void setCacheNameShouldRemovePreviousDynamicCaches() {
292+
CaffeineCacheManager manager = new CaffeineCacheManager();
293+
294+
// Given: Dynamic mode with some caches
295+
manager.getCache("dynamicCache1");
296+
manager.getCache("dynamicCache2");
297+
assertThat(manager.getCacheNames())
298+
.containsExactlyInAnyOrder("dynamicCache1", "dynamicCache2");
299+
300+
// When: Switch to static mode
301+
manager.setCacheNames(Arrays.asList("staticCache1", "staticCache2"));
302+
303+
// Then: Only static caches should exist
304+
assertThat(manager.getCacheNames())
305+
.containsExactlyInAnyOrder("staticCache1", "staticCache2")
306+
.as("Dynamic caches should be removed when switching to static mode");
307+
308+
// And: Dynamic caches should not be accessible
309+
assertThat(manager.getCache("dynamicCache1")).isNull();
310+
assertThat(manager.getCache("dynamicCache2")).isNull();
311+
}
312+
313+
@Test
314+
void setCacheNamesShouldPreserveCustomCaches() {
315+
CaffeineCacheManager manager = new CaffeineCacheManager();
316+
317+
// Given: Custom cache registered
318+
com.github.benmanes.caffeine.cache.Cache<Object, @Nullable Object> customNativeCache = Caffeine.newBuilder().maximumSize(100).build();
319+
manager.registerCustomCache("customCache", customNativeCache);
320+
321+
// And: Dynamic cache created
322+
manager.getCache("dynamicCache");
323+
324+
// When: Switch to static mode
325+
manager.setCacheNames(List.of("staticCache"));
326+
327+
// Then: Custom cache preserved, dynamic cache removed
328+
assertThat(manager.getCacheNames())
329+
.contains("customCache", "staticCache")
330+
.doesNotContain("dynamicCache");
331+
}
332+
333+
@Test
334+
void switchingBetweenDynamicAndStaticMode() {
335+
CaffeineCacheManager manager = new CaffeineCacheManager();
336+
337+
// Dynamic → Static
338+
manager.getCache("dynamicCache1");
339+
manager.setCacheNames(List.of("staticCache1"));
340+
assertThat(manager.getCacheNames()).containsExactly("staticCache1");
341+
342+
// Static → Dynamic
343+
manager.setCacheNames(null); // Re-enable dynamic mode
344+
manager.getCache("dynamicCache2");
345+
assertThat(manager.getCacheNames()).contains("staticCache1", "dynamicCache2");
346+
347+
// Dynamic → Static again
348+
manager.setCacheNames(List.of("staticCache2"));
349+
assertThat(manager.getCacheNames()).containsExactly("staticCache2");
350+
}
351+
287352
@Test
288353
void cacheLoaderUseLoadingCache() {
289354
CaffeineCacheManager cm = new CaffeineCacheManager("c1");

0 commit comments

Comments
 (0)