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

dmsolr 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 3630671e20 fix(plugin-dubbo): reconcile gray selector caches on full 
handle update (#7814)
3630671e20 is described below

commit 3630671e20f7db4ef9751bba46bfa60fd1a906a5
Author: Daming <[email protected]>
AuthorDate: Mon Oct 19 11:42:06 2026 +0800

    fix(plugin-dubbo): reconcile gray selector caches on full handle update 
(#7814)
    
    A full selector-handle update did not reconcile the Dubbo gray routing 
caches with the latest configured gray targets:
    
    - An empty handle ([]) returned early, leaving SELECTOR_CACHED_HANDLE and 
UpstreamCacheManager populated with the previously cached gray targets, so gray 
routing stayed active after the configuration was cleared.
    - Replacing gray target A with B updated the selector handle cache to B, 
but UpstreamCacheManager.submit merged B into the existing state without 
removing the omitted A, so A remained a routing candidate.
    
    ApacheDubboGrayLoadBalance selects candidates from UpstreamCacheManager 
whenever the selector handle cache is nonempty, so the retained targets kept 
receiving traffic.
    
    Treat the full handle as the authoritative gray target set: clear the 
selector's stale upstream state before submitting the new gray list so omitted 
targets are removed from both the routing and health-check caches, and clear 
the gray caches for an empty/non-gray handle without touching unrelated rule 
configuration. The shared manager's incremental submit semantics are preserved 
for other callers.
    
    Co-authored-by: Luke Haochao Zhuang <[email protected]>
---
 .../handler/AbstractDubboPluginDataHandler.java    | 28 +++++++++----
 .../AbstractDubboPluginDataHandlerTest.java        | 48 ++++++++++++++++++++++
 2 files changed, 67 insertions(+), 9 deletions(-)

diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
index 0eaae274bc..101765d05c 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/main/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandler.java
@@ -80,22 +80,32 @@ public abstract class AbstractDubboPluginDataHandler 
implements PluginDataHandle
         if (!selectorData.getContinued()) {
             
RULE_CACHED_HANDLE.get().cachedHandle(CacheKeyUtils.INST.getKey(selectorData.getId(),
 Constants.DEFAULT_RULE), DubboRuleHandle.newInstance());
         }
-        List<DubboUpstream> dubboUpstreams = 
GsonUtils.getInstance().fromList(selectorData.getHandle(), DubboUpstream.class);
-        if (CollectionUtils.isEmpty(dubboUpstreams)) {
+        final String selectorId = selectorData.getId();
+        if (StringUtils.isBlank(selectorId)) {
+            // a selector without an identity has no gray routing caches to 
reconcile
             return;
         }
+        List<DubboUpstream> dubboUpstreams = 
GsonUtils.getInstance().fromList(selectorData.getHandle(), DubboUpstream.class);
         List<DubboUpstream> graySelectorHandle = new ArrayList<>();
-        for (DubboUpstream each : dubboUpstreams) {
-            if (StringUtils.isNotBlank(each.getUpstreamUrl()) && 
Objects.nonNull(each.isGray()) && Boolean.TRUE.equals(each.isGray())) {
-                graySelectorHandle.add(each);
+        if (CollectionUtils.isNotEmpty(dubboUpstreams)) {
+            for (DubboUpstream each : dubboUpstreams) {
+                if (StringUtils.isNotBlank(each.getUpstreamUrl()) && 
Objects.nonNull(each.isGray()) && Boolean.TRUE.equals(each.isGray())) {
+                    graySelectorHandle.add(each);
+                }
             }
         }
         if (CollectionUtils.isNotEmpty(graySelectorHandle)) {
-            SELECTOR_CACHED_HANDLE.get().cachedHandle(selectorData.getId(), 
graySelectorHandle);
-            UpstreamCacheManager.getInstance().submit(selectorData.getId(), 
convertUpstreamList(graySelectorHandle));
+            // A full selector-handle update must reconcile the gray routing 
candidates with the latest gray
+            // targets. Clear the selector's stale upstream state first so 
that omitted targets are removed from
+            // both the routing and health-check caches, instead of being 
merged and retained by submit.
+            UpstreamCacheManager.getInstance().removeByKey(selectorId);
+            SELECTOR_CACHED_HANDLE.get().cachedHandle(selectorId, 
graySelectorHandle);
+            UpstreamCacheManager.getInstance().submit(selectorId, 
convertUpstreamList(graySelectorHandle));
         } else {
-            // if update gray selector is empty, remove cache
-            removeSelector(selectorData);
+            // An empty or non-gray handle is a valid configuration 
replacement: clear the gray routing caches so
+            // the removed targets stop being candidates, without touching 
unrelated rule configuration.
+            SELECTOR_CACHED_HANDLE.get().removeHandle(selectorId);
+            UpstreamCacheManager.getInstance().removeByKey(selectorId);
         }
     }
 
diff --git 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/test/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandlerTest.java
 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/test/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandlerTest.java
index 01ea18db3b..24056880da 100644
--- 
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/test/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandlerTest.java
+++ 
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-dubbo/shenyu-plugin-dubbo-common/src/test/java/org/apache/shenyu/plugin/dubbo/common/handler/AbstractDubboPluginDataHandlerTest.java
@@ -22,6 +22,8 @@ import org.apache.shenyu.common.dto.RuleData;
 import org.apache.shenyu.common.dto.SelectorData;
 import org.apache.shenyu.common.dto.convert.plugin.DubboRegisterConfig;
 import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.loadbalancer.cache.UpstreamCacheManager;
+import org.apache.shenyu.loadbalancer.entity.Upstream;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -29,6 +31,8 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import org.mockito.junit.jupiter.MockitoSettings;
 import org.mockito.quality.Strictness;
 
+import java.util.List;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -95,6 +99,50 @@ public class AbstractDubboPluginDataHandlerTest {
         
assertNull(AbstractDubboPluginDataHandler.SELECTOR_CACHED_HANDLE.get().obtainHandle("1"));
     }
 
+    @Test
+    public void emptyHandleClearsGrayRoutingCaches() {
+        final String selectorId = "gray-empty-selector";
+        UpstreamCacheManager.getInstance().removeByKey(selectorId);
+        SelectorData selectorData = new SelectorData();
+        selectorData.setId(selectorId);
+        selectorData.setContinued(true);
+        
selectorData.setHandle("[{\"protocol\":\"dubbo://\",\"upstreamUrl\":\"127.0.0.1:20880\",\"gray\":true,"
+                + 
"\"weight\":1,\"status\":true,\"healthCheckEnabled\":false}]");
+        handler.handlerSelector(selectorData);
+        assertEquals(1, 
AbstractDubboPluginDataHandler.SELECTOR_CACHED_HANDLE.get().obtainHandle(selectorId).size());
+        assertEquals(1, 
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selectorId).size());
+
+        // a full update to an empty handle is a valid replacement and must 
clear gray routing
+        selectorData.setHandle("[]");
+        handler.handlerSelector(selectorData);
+        
assertNull(AbstractDubboPluginDataHandler.SELECTOR_CACHED_HANDLE.get().obtainHandle(selectorId));
+        
assertNull(UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selectorId));
+        UpstreamCacheManager.getInstance().removeByKey(selectorId);
+    }
+
+    @Test
+    public void replacingGrayTargetRemovesOmittedUpstream() {
+        final String selectorId = "gray-replace-selector";
+        UpstreamCacheManager.getInstance().removeByKey(selectorId);
+        SelectorData selectorData = new SelectorData();
+        selectorData.setId(selectorId);
+        selectorData.setContinued(true);
+        
selectorData.setHandle("[{\"protocol\":\"dubbo://\",\"upstreamUrl\":\"127.0.0.1:20880\",\"gray\":true,"
+                + 
"\"weight\":1,\"status\":true,\"healthCheckEnabled\":false}]");
+        handler.handlerSelector(selectorData);
+        assertEquals(1, 
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selectorId).size());
+
+        // replace gray target A (20880) with B (20881): A must no longer 
remain a routing candidate
+        
selectorData.setHandle("[{\"protocol\":\"dubbo://\",\"upstreamUrl\":\"127.0.0.1:20881\",\"gray\":true,"
+                + 
"\"weight\":1,\"status\":true,\"healthCheckEnabled\":false}]");
+        handler.handlerSelector(selectorData);
+        List<Upstream> candidates = 
UpstreamCacheManager.getInstance().findUpstreamListBySelectorId(selectorId);
+        assertEquals(1, candidates.size());
+        assertEquals("127.0.0.1:20881", candidates.get(0).getUrl());
+        assertEquals(1, 
AbstractDubboPluginDataHandler.SELECTOR_CACHED_HANDLE.get().obtainHandle(selectorId).size());
+        UpstreamCacheManager.getInstance().removeByKey(selectorId);
+    }
+
     @Test
     public void ruleTest() {
         RuleData ruleData = new RuleData();

Reply via email to