This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new 6dc19834f2 fix(plugin): prevent expired memory cache entries from 
accumulating (#7745)
6dc19834f2 is described below

commit 6dc19834f2135365e476bffdafa2c44bda5f44be
Author: lymerin <[email protected]>
AuthorDate: Mon Oct 19 09:29:10 2026 +0800

    fix(plugin): prevent expired memory cache entries from accumulating (#7745)
---
 .../shenyu-plugin-cache-memory/pom.xml             |  4 ++
 .../shenyu/plugin/cache/memory/MemoryCache.java    | 55 +++++++++++--------
 .../plugin/cache/memory/MemoryCacheTest.java       | 62 ++++++++++++++++++----
 3 files changed, 88 insertions(+), 33 deletions(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/pom.xml 
b/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/pom.xml
index be179c1480..be1860cc74 100644
--- a/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/pom.xml
+++ b/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/pom.xml
@@ -27,6 +27,10 @@
     <artifactId>shenyu-plugin-cache-memory</artifactId>
 
     <dependencies>
+        <dependency>
+            <groupId>com.github.ben-manes.caffeine</groupId>
+            <artifactId>caffeine</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.shenyu</groupId>
             <artifactId>shenyu-plugin-cache-spi</artifactId>
diff --git 
a/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/main/java/org/apache/shenyu/plugin/cache/memory/MemoryCache.java
 
b/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/main/java/org/apache/shenyu/plugin/cache/memory/MemoryCache.java
index b51553431c..97e7073d86 100644
--- 
a/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/main/java/org/apache/shenyu/plugin/cache/memory/MemoryCache.java
+++ 
b/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/main/java/org/apache/shenyu/plugin/cache/memory/MemoryCache.java
@@ -17,15 +17,15 @@
 
 package org.apache.shenyu.plugin.cache.memory;
 
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheBuilder;
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.Expiry;
+import com.github.benmanes.caffeine.cache.Scheduler;
 import org.apache.shenyu.plugin.cache.ICache;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 
-import java.util.Map;
 import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -33,10 +33,28 @@ import java.util.concurrent.TimeUnit;
  */
 public final class MemoryCache implements ICache {
 
-    private final Map<String, Cache<String, byte[]>> mainCache;
+    private final Cache<String, byte[]> mainCache;
 
     public MemoryCache() {
-        this.mainCache = new ConcurrentHashMap<>();
+        // Entries must be written through VarExpiration.put(...) to receive 
their per-entry TTL.
+        this.mainCache = Caffeine.newBuilder()
+                .scheduler(Scheduler.systemScheduler())
+                .expireAfter(new Expiry<String, byte[]>() {
+                    @Override
+                    public long expireAfterCreate(final String key, final 
byte[] value, final long currentTime) {
+                        return 0;
+                    }
+
+                    @Override
+                    public long expireAfterUpdate(final String key, final 
byte[] value, final long currentTime, final long currentDuration) {
+                        return 0;
+                    }
+
+                    @Override
+                    public long expireAfterRead(final String key, final byte[] 
value, final long currentTime, final long currentDuration) {
+                        return currentDuration;
+                    }
+                }).build();
     }
 
     /**
@@ -49,9 +67,10 @@ public final class MemoryCache implements ICache {
      */
     @Override
     public Mono<Boolean> cacheData(final String key, final byte[] bytes, final 
long timeoutSeconds) {
-        final Cache<String, byte[]> cache = 
CacheBuilder.newBuilder().expireAfterWrite(timeoutSeconds, 
TimeUnit.SECONDS).build();
-        cache.put(key, bytes);
-        this.mainCache.put(key, cache);
+        // Write the value and its TTL together, overriding the default expiry 
callbacks.
+        this.mainCache.policy().expireVariably()
+                .orElseThrow(() -> new IllegalStateException("Variable 
expiration is not configured for the memory cache"))
+                .put(key, bytes, timeoutSeconds, TimeUnit.SECONDS);
         return Mono.fromCallable(() -> 
Boolean.TRUE).subscribeOn(Schedulers.boundedElastic());
     }
 
@@ -63,13 +82,7 @@ public final class MemoryCache implements ICache {
      */
     @Override
     public Mono<Boolean> isExist(final String key) {
-        final Cache<String, byte[]> cache = this.mainCache.get(key);
-        if (Objects.isNull(cache) || !cache.asMap().containsKey(key)) {
-            // remove from main cache
-            this.mainCache.remove(key);
-            return Mono.fromCallable(() -> 
Boolean.FALSE).subscribeOn(Schedulers.boundedElastic());
-        }
-        return Mono.fromCallable(() -> 
Boolean.TRUE).subscribeOn(Schedulers.boundedElastic());
+        return Mono.fromCallable(() -> 
Objects.nonNull(this.mainCache.getIfPresent(key))).subscribeOn(Schedulers.boundedElastic());
     }
 
     /**
@@ -80,12 +93,7 @@ public final class MemoryCache implements ICache {
      */
     @Override
     public Mono<byte[]> getData(final String key) {
-        return isExist(key).mapNotNull(exist -> {
-            if (exist) {
-                return this.mainCache.get(key).asMap().get(key);
-            }
-            return null;
-        });
+        return Mono.fromCallable(() -> 
this.mainCache.getIfPresent(key)).subscribeOn(Schedulers.boundedElastic());
     }
 
     /**
@@ -93,6 +101,7 @@ public final class MemoryCache implements ICache {
      */
     @Override
     public void close() {
-        this.mainCache.clear();
+        this.mainCache.invalidateAll();
+        this.mainCache.cleanUp();
     }
 }
diff --git 
a/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/test/java/org/apache/shenyu/plugin/cache/memory/MemoryCacheTest.java
 
b/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/test/java/org/apache/shenyu/plugin/cache/memory/MemoryCacheTest.java
index ee3cd39a4c..132f485c4a 100644
--- 
a/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/test/java/org/apache/shenyu/plugin/cache/memory/MemoryCacheTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-cache/shenyu-plugin-cache-memory/src/test/java/org/apache/shenyu/plugin/cache/memory/MemoryCacheTest.java
@@ -17,29 +17,71 @@
 
 package org.apache.shenyu.plugin.cache.memory;
 
-import org.junit.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.mockito.junit.jupiter.MockitoExtension;
+import com.github.benmanes.caffeine.cache.Cache;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
 
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.awaitility.Awaitility.await;
 
 /**
  * MemoryCacheTest.
  */
-@ExtendWith(MockitoExtension.class)
 public class MemoryCacheTest {
 
+    private final MemoryCache memoryCache = new MemoryCache();
+
+    @AfterEach
+    public void tearDown() {
+        memoryCache.close();
+    }
+
     @Test
     public void testMemoryCache() {
-        final MemoryCache memoryCache = new MemoryCache();
         final String key = "data";
-        memoryCache.isExist(key).subscribe(v -> assertEquals(Boolean.FALSE, 
v));
-        memoryCache.cacheData(key, "data".getBytes(StandardCharsets.UTF_8), 10)
-                .subscribe(v -> assertEquals(Boolean.TRUE, v));
-        memoryCache.isExist(key).subscribe(v -> assertEquals(Boolean.TRUE, v));
-        memoryCache.getData(key).subscribe(v -> assertEquals("data", new 
String(v, StandardCharsets.UTF_8)));
+        final byte[] bytes = "data".getBytes(StandardCharsets.UTF_8);
+        assertEquals(Boolean.FALSE, memoryCache.isExist(key).block());
+        assertEquals(Boolean.TRUE, memoryCache.cacheData(key, bytes, 
10).block());
+        assertEquals(Boolean.TRUE, memoryCache.isExist(key).block());
+        assertArrayEquals(bytes, memoryCache.getData(key).block());
+        memoryCache.close();
+        assertEquals(Boolean.FALSE, memoryCache.isExist(key).block());
+        assertNull(memoryCache.getData(key).block());
+    }
+
+    @Test
+    public void testExpiredEntriesAreRemovedWithoutReads() {
+        for (int i = 0; i < 100; i++) {
+            memoryCache.cacheData("key-" + i, new byte[]{1}, 1).block();
+        }
+        Cache<?, ?> cache = (Cache<?, ?>) 
ReflectionTestUtils.getField(memoryCache, "mainCache");
+        // estimatedSize observes retained entries without triggering reads or 
maintenance.
+        await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> 
assertEquals(0L, cache.estimatedSize()));
+    }
+
+    @Test
+    public void testPerEntryExpirationAndReplacement() {
+        final byte[] bytes = "data".getBytes(StandardCharsets.UTF_8);
+        memoryCache.cacheData("short", bytes, 1).block();
+        memoryCache.cacheData("long", bytes, 60).block();
+        memoryCache.cacheData("extended", bytes, 1).block();
+        memoryCache.cacheData("extended", bytes, 60).block();
+        memoryCache.cacheData("shortened", bytes, 60).block();
+        memoryCache.cacheData("shortened", bytes, 1).block();
+
+        // Polling the short entry must not extend its write TTL.
+        await().pollInterval(50, TimeUnit.MILLISECONDS).atMost(10, 
TimeUnit.SECONDS).untilAsserted(() -> {
+            assertNull(memoryCache.getData("short").block());
+            assertNull(memoryCache.getData("shortened").block());
+        });
+        assertArrayEquals(bytes, memoryCache.getData("long").block());
+        assertArrayEquals(bytes, memoryCache.getData("extended").block());
     }
 
 }

Reply via email to