From b5633bb34473da068309f90e90129db534add937 Mon Sep 17 00:00:00 2001 From: Mustafa Senoglu Date: Tue, 21 Jul 2026 15:36:32 +0300 Subject: [PATCH] fix: CacheManager memory leak by reusing InheritableThreadLocal CacheManager.clearCache() was creating a new InheritableThreadLocal instance on every call without calling .remove() on the previous one. This caused a progressive memory leak that grows with iteration_count x thread_count when 'Clear cache each iteration' is enabled. The existing TODO comment acknowledged this: // TODO: avoid re-creating the thread local every time, // reset its contents instead Fix by initializing threadCache once at field declaration and calling threadCache.remove() in clearCache() instead of creating a new instance. The next .get() call will lazily create a fresh cache via initialValue(). Also added @SuppressWarnings for ErrorProne's ThreadLocalUsage since InheritableThreadLocal is used intentionally for per-thread caching. Fixes #6730 Signed-off-by: Mustafa Senoglu --- .../protocol/http/control/CacheManager.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java index 8852f6332e4..9ca0f9a2f5e 100644 --- a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java +++ b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/control/CacheManager.java @@ -80,7 +80,16 @@ public class CacheManager extends ConfigTestElement implements TestStateListener public static final String MAX_SIZE = "maxSize"; // $NON-NLS-1$ //- - private transient InheritableThreadLocal> threadCache; + @SuppressWarnings("ThreadLocalUsage") // intentionally using InheritableThreadLocal for per-thread cache + private final transient InheritableThreadLocal> threadCache = + new InheritableThreadLocal>(){ + @Override + protected Cache initialValue() { + return Caffeine.newBuilder() + .maximumSize(getMaxSize()) + .build(); + } + }; private transient boolean useExpires; // Cached value @@ -602,15 +611,7 @@ public void clear(){ private void clearCache() { log.debug("Clear cache"); - // TODO: avoid re-creating the thread local every time, reset its contents instead - threadCache = new InheritableThreadLocal>(){ - @Override - protected Cache initialValue() { - return Caffeine.newBuilder() - .maximumSize(getMaxSize()) - .build(); - } - }; + threadCache.remove(); } /**