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 e9fafc8174 fix(admin): avoid double-prefixing cache keys in HTTP sync 
recovery (#7447)
e9fafc8174 is described below

commit e9fafc817416af946fbec57e8f7b83b379902eb7
Author: lymerin <[email protected]>
AuthorDate: Sat Oct 10 08:54:34 2026 +0800

    fix(admin): avoid double-prefixing cache keys in HTTP sync recovery (#7447)
    
    Fixes #7436
---
 .../http/HttpLongPollingDataChangedListener.java   |  4 +-
 .../HttpLongPollingDataChangedListenerTest.java    | 78 ++++++++++++++++------
 2 files changed, 60 insertions(+), 22 deletions(-)

diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListener.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListener.java
index 75ee6860d7..f93b942660 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListener.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListener.java
@@ -254,7 +254,9 @@ public class HttpLongPollingDataChangedListener extends 
AbstractDataChangedListe
         // Considering the concurrency problem, admin must lock,
         // otherwise it may cause the request from shenyu-web to update the 
cache concurrently, causing excessive db pressure
 
-        String configDataCacheKey = 
buildCacheKey(serverCache.getNamespaceId(), serverCache.getGroup());
+        // updateCache stores the namespace-qualified cache key in 
ConfigDataCache.group.
+        // Reuse it directly to avoid adding the namespace prefix twice.
+        String configDataCacheKey = serverCache.getGroup();
 
         ConfigDataCache latest = CACHE.get(configDataCacheKey);
         if (latest != serverCache) {
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListenerTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListenerTest.java
index a9b33f00d3..a888f0e7d7 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListenerTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/http/HttpLongPollingDataChangedListenerTest.java
@@ -39,6 +39,8 @@ import org.apache.shenyu.common.utils.GsonUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
@@ -62,6 +64,7 @@ import java.util.concurrent.ConcurrentMap;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.mockito.ArgumentMatchers.any;
 
 /**
@@ -274,7 +277,7 @@ public final class HttpLongPollingDataChangedListenerTest {
         String namespaceId = DEFAULT_NAMESPACE;
         String group = ConfigGroupEnum.PLUGIN.name();
         String cacheKey = 
HttpLongPollingDataChangedListener.buildCacheKey(namespaceId, group);
-        ConfigDataCache cache = new ConfigDataCache(group, "{}", "md5", 1000L, 
namespaceId);
+        ConfigDataCache cache = new ConfigDataCache(cacheKey, "{}", "md5", 
1000L, namespaceId);
         getCache().put(cacheKey, cache);
 
         boolean result = invokeCheckCacheDelayAndUpdate(cache, "md5", 500L);
@@ -295,8 +298,8 @@ public final class HttpLongPollingDataChangedListenerTest {
         String cacheKey = 
HttpLongPollingDataChangedListener.buildCacheKey(namespaceId, group);
 
         // serverCache is NOT the object in CACHE; CACHE has a different 
instance with same md5 as client
-        ConfigDataCache serverCache = new ConfigDataCache(group, "{}", "md5", 
1000L, namespaceId);
-        ConfigDataCache latestCache = new ConfigDataCache(group, "{}", 
"clientMd5", 2000L, namespaceId);
+        ConfigDataCache serverCache = new ConfigDataCache(cacheKey, "{}", 
"md5", 1000L, namespaceId);
+        ConfigDataCache latestCache = new ConfigDataCache(cacheKey, "{}", 
"clientMd5", 2000L, namespaceId);
         getCache().put(cacheKey, latestCache);
 
         // clientMd5 matches latestCache.md5 → returns false (no update needed)
@@ -314,7 +317,7 @@ public final class HttpLongPollingDataChangedListenerTest {
         String namespaceId = DEFAULT_NAMESPACE;
         String group = ConfigGroupEnum.PLUGIN.name();
         String cacheKey = 
HttpLongPollingDataChangedListener.buildCacheKey(namespaceId, group);
-        ConfigDataCache cache = new ConfigDataCache(group, "{}", "md5", 1000L, 
namespaceId);
+        ConfigDataCache cache = new ConfigDataCache(cacheKey, "{}", "md5", 
1000L, namespaceId);
         getCache().put(cacheKey, cache);
 
         boolean result = invokeCheckCacheDelayAndUpdate(cache, "newMd5", 500L);
@@ -332,8 +335,8 @@ public final class HttpLongPollingDataChangedListenerTest {
         String group = ConfigGroupEnum.PLUGIN.name();
         String cacheKey = 
HttpLongPollingDataChangedListener.buildCacheKey(namespaceId, group);
 
-        ConfigDataCache oldCache = new ConfigDataCache(group, "{}", "oldMd5", 
500L, namespaceId);
-        ConfigDataCache newCache = new ConfigDataCache(group, "{}", "newMd5", 
2000L, namespaceId);
+        ConfigDataCache oldCache = new ConfigDataCache(cacheKey, "{}", 
"oldMd5", 500L, namespaceId);
+        ConfigDataCache newCache = new ConfigDataCache(cacheKey, "{}", 
"newMd5", 2000L, namespaceId);
         getCache().put(cacheKey, newCache);
 
         // oldCache != CACHE.get(cacheKey), clientMd5 != newMd5 → true
@@ -347,21 +350,46 @@ public final class HttpLongPollingDataChangedListenerTest 
{
         getCache().remove(cacheKey);
     }
 
-    @Test
-    public void testClientNewerRefreshesOnlyItsNamespace() throws Exception {
+    @ParameterizedTest
+    @ValueSource(booleans = {true, false})
+    public void testClientNewerRefreshesOnlyItsNamespace(final boolean 
matchesClient) throws Exception {
         RecordingHttpLongPollingDataChangedListener recordingListener =
-                new 
RecordingHttpLongPollingDataChangedListener(httpSyncProperties);
+                new 
RecordingHttpLongPollingDataChangedListener(httpSyncProperties, matchesClient ? 
"client" : "refreshed");
         String namespaceId = "namespace-one";
-        String group = ConfigGroupEnum.PLUGIN.name();
-        String cacheKey = 
HttpLongPollingDataChangedListener.buildCacheKey(namespaceId, group);
-        ConfigDataCache serverCache = new ConfigDataCache(group, "{}", 
"serverMd5", 1000L, namespaceId);
-        getCache().put(cacheKey, serverCache);
+        try {
+            ConfigDataCache clientCache = 
recordingListener.seedCache(namespaceId, "client");
+            ConfigDataCache serverCache = 
recordingListener.seedCache(namespaceId, "old");
 
-        boolean result = invokeCheckCacheDelayAndUpdate(recordingListener, 
serverCache, "clientMd5", 2000L);
+            boolean result = invokeCheckCacheDelayAndUpdate(recordingListener, 
serverCache,
+                    clientCache.getMd5(), serverCache.getLastModifyTime() + 1);
 
-        assertEquals(true, result);
-        assertEquals(namespaceId, recordingListener.refreshedNamespace);
-        getCache().remove(cacheKey);
+            assertEquals(!matchesClient, result);
+            assertEquals(namespaceId, recordingListener.refreshedNamespace);
+        } finally {
+            clearAllGroupsInCache(namespaceId);
+        }
+    }
+
+    @Test
+    public void testRecoveryDoesNotReadAnotherNamespace() throws Exception {
+        String namespaceId = "ns1";
+        String otherNamespaceId = "ns1_ns1";
+        RecordingHttpLongPollingDataChangedListener recordingListener =
+                new 
RecordingHttpLongPollingDataChangedListener(httpSyncProperties, "refreshed");
+        try {
+            ConfigDataCache otherCache = 
recordingListener.seedCache(otherNamespaceId, "client");
+            ConfigDataCache serverCache = 
recordingListener.seedCache(namespaceId, "old");
+
+            boolean result = invokeCheckCacheDelayAndUpdate(recordingListener, 
serverCache,
+                    otherCache.getMd5(), serverCache.getLastModifyTime() + 1);
+
+            assertEquals(true, result);
+            assertEquals(namespaceId, recordingListener.refreshedNamespace);
+            assertSame(otherCache, getCache().get(otherCache.getGroup()));
+        } finally {
+            clearAllGroupsInCache(namespaceId);
+            clearAllGroupsInCache(otherNamespaceId);
+        }
     }
 
     /**
@@ -730,7 +758,7 @@ public final class HttpLongPollingDataChangedListenerTest {
     private void populateAllGroupsInCache(final String namespaceId, final 
String md5) throws Exception {
         for (ConfigGroupEnum group : ConfigGroupEnum.values()) {
             String cacheKey = 
HttpLongPollingDataChangedListener.buildCacheKey(namespaceId, group.name());
-            getCache().put(cacheKey, new ConfigDataCache(group.name(), "{}", 
md5, 1000L, namespaceId));
+            getCache().put(cacheKey, new ConfigDataCache(cacheKey, "{}", md5, 
1000L, namespaceId));
         }
     }
 
@@ -812,17 +840,25 @@ public final class HttpLongPollingDataChangedListenerTest 
{
 
     private static final class RecordingHttpLongPollingDataChangedListener 
extends HttpLongPollingDataChangedListener {
 
+        private final String refreshedId;
+
         private String refreshedNamespace;
 
-        private RecordingHttpLongPollingDataChangedListener(final 
HttpSyncProperties httpSyncProperties) {
+        private RecordingHttpLongPollingDataChangedListener(final 
HttpSyncProperties httpSyncProperties, final String refreshedId) {
             super(httpSyncProperties);
+            this.refreshedId = refreshedId;
+        }
+
+        private ConfigDataCache seedCache(final String namespaceId, final 
String id) {
+            PluginData data = 
PluginData.builder().id(id).namespaceId(namespaceId).build();
+            updateCache(ConfigGroupEnum.PLUGIN, List.of(data), namespaceId);
+            return CACHE.get(buildCacheKey(namespaceId, 
ConfigGroupEnum.PLUGIN.name()));
         }
 
         @Override
         protected void refreshLocalCache(final String namespaceId) {
             refreshedNamespace = namespaceId;
-            String cacheKey = buildCacheKey(namespaceId, 
ConfigGroupEnum.PLUGIN.name());
-            CACHE.put(cacheKey, new 
ConfigDataCache(ConfigGroupEnum.PLUGIN.name(), "{}", "refreshedMd5", 3000L, 
namespaceId));
+            seedCache(namespaceId, refreshedId);
         }
     }
 }

Reply via email to