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

moremind 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 c8961528b7 Prevent duplicate full-mode URI heartbeats after repeated 
context refresh (#6337)
c8961528b7 is described below

commit c8961528b72a2a7f81f0d1cd7525af1659a670bc
Author: aias00 <[email protected]>
AuthorDate: Fri May 8 16:17:42 2026 +0800

    Prevent duplicate full-mode URI heartbeats after repeated context refresh 
(#6337)
    
    Full-mode client listeners published URI registration before the shared
    one-time registration gate ran, so repeated ContextRefreshedEvent
    invocations could enqueue the same URI multiple times and amplify
    heartbeat logging. This change routes full-mode registration through the
    same gate as the standard listener flow and locks the behavior with
    Spring MVC and WebSocket regression tests.
    
    Constraint: Full-mode registration must preserve standard one-shot listener 
semantics
    Rejected: Deduplicate in the heartbeat subscriber | leaves duplicate 
registration side effects in place
    Confidence: high
    Scope-risk: narrow
    Reversibility: clean
    Directive: Keep any full-mode registration path behind the shared 
registration gate
    Tested: mvn -pl 
shenyu-client/shenyu-client-core,shenyu-client/shenyu-client-http/shenyu-client-springmvc,shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket
 -am -DfailIfNoTests=false 
-Dtest=ShenyuClientURIExecutorSubscriberTest,SpringMvcClientEventListenerTest,SpringWebSocketClientEventListenerTest
 test
    Not-tested: Live expos-admin-server runtime verification
---
 .../AbstractContextRefreshedEventListener.java     | 11 ++++-
 .../init/SpringMvcClientEventListener.java         |  8 ++--
 .../init/SpringMvcClientEventListenerTest.java     | 16 +++++++
 .../init/SpringWebSocketClientEventListener.java   |  8 ++--
 .../SpringWebSocketClientEventListenerTest.java    | 54 +++++++++++++++-------
 5 files changed, 72 insertions(+), 25 deletions(-)

diff --git 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
index 2e55cc4c59..32ef07fcda 100644
--- 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
+++ 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
@@ -150,7 +150,7 @@ public abstract class 
AbstractContextRefreshedEventListener<T, A extends Annotat
         if (MapUtils.isEmpty(beans)) {
             return;
         }
-        if (!registered.compareAndSet(false, true)) {
+        if (!markRegistered()) {
             return;
         }
         if (isDiscoveryLocalMode) {
@@ -372,6 +372,15 @@ public abstract class 
AbstractContextRefreshedEventListener<T, A extends Annotat
         return publisher;
     }
 
+    /**
+     * Mark the listener as registered once.
+     *
+     * @return true when this invocation acquires the registration gate
+     */
+    protected boolean markRegistered() {
+        return registered.compareAndSet(false, true);
+    }
+
     /**
      * Get the metadata map.
      *
diff --git 
a/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/main/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListener.java
 
b/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/main/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListener.java
index d2f89f8816..17e8f3145c 100644
--- 
a/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/main/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListener.java
+++ 
b/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/main/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListener.java
@@ -21,7 +21,6 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import 
org.apache.shenyu.client.core.client.AbstractContextRefreshedEventListener;
 import org.apache.shenyu.client.core.constant.ShenyuClientConstants;
-import 
org.apache.shenyu.client.core.disruptor.ShenyuClientRegisterEventPublisher;
 import org.apache.shenyu.client.core.utils.PortUtils;
 import org.apache.shenyu.client.springmvc.annotation.ShenyuSpringMvcClient;
 import org.apache.shenyu.common.enums.ApiHttpMethodEnum;
@@ -68,8 +67,6 @@ public class SpringMvcClientEventListener extends 
AbstractContextRefreshedEventL
 
     private static final Logger LOG = 
LoggerFactory.getLogger(SpringMvcClientEventListener.class);
 
-    private final ShenyuClientRegisterEventPublisher publisher = 
ShenyuClientRegisterEventPublisher.getInstance();
-
     private final List<Class<? extends Annotation>> mappingAnnotation = new 
ArrayList<>(3);
 
     private final Boolean isFull;
@@ -121,6 +118,9 @@ public class SpringMvcClientEventListener extends 
AbstractContextRefreshedEventL
     protected Map<String, Object> getBeans(final ApplicationContext context) {
         // Filter out
         if (Boolean.TRUE.equals(isFull)) {
+            if (!markRegistered()) {
+                return Collections.emptyMap();
+            }
             LOG.info("init spring mvc client success with isFull mode");
             List<String> namespaceIds = super.getNamespace();
             namespaceIds.forEach(namespaceId -> {
@@ -135,7 +135,7 @@ public class SpringMvcClientEventListener extends 
AbstractContextRefreshedEventL
                         .namespaceId(namespaceId)
                         .build());
                 
-                publisher.publishEvent(buildURIRegisterDTO(context, 
Collections.emptyMap(), namespaceId));
+                getPublisher().publishEvent(buildURIRegisterDTO(context, 
Collections.emptyMap(), namespaceId));
             });
             return Collections.emptyMap();
         }
diff --git 
a/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/test/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListenerTest.java
 
b/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/test/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListenerTest.java
index a9e4ba8c49..33ec3f1066 100644
--- 
a/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/test/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListenerTest.java
+++ 
b/shenyu-client/shenyu-client-http/shenyu-client-springmvc/src/test/java/org/apache/shenyu/client/springmvc/init/SpringMvcClientEventListenerTest.java
@@ -18,6 +18,7 @@
 package org.apache.shenyu.client.springmvc.init;
 
 import org.apache.shenyu.client.core.constant.ShenyuClientConstants;
+import 
org.apache.shenyu.client.core.disruptor.ShenyuClientRegisterEventPublisher;
 import 
org.apache.shenyu.client.core.exception.ShenyuClientIllegalArgumentException;
 import 
org.apache.shenyu.client.core.register.ShenyuClientRegisterRepositoryFactory;
 import org.apache.shenyu.client.springmvc.annotation.ShenyuSpringMvcClient;
@@ -178,6 +179,21 @@ public class SpringMvcClientEventListenerTest {
         registerUtilsMockedStatic.close();
     }
 
+    @Test
+    public void testOnApplicationEventFullModeShouldRegisterOnce() {
+        try (MockedStatic<ShenyuClientRegisterEventPublisher> 
publisherMockedStatic = mockStatic(ShenyuClientRegisterEventPublisher.class)) {
+            ShenyuClientRegisterEventPublisher publisher = 
mock(ShenyuClientRegisterEventPublisher.class);
+            
publisherMockedStatic.when(ShenyuClientRegisterEventPublisher::getInstance).thenReturn(publisher);
+            SpringMvcClientEventListener springMvcClientEventListener = 
buildSpringMvcClientEventListener(true, true);
+            ContextRefreshedEvent event = new 
ContextRefreshedEvent(applicationContext);
+            springMvcClientEventListener.onApplicationEvent(event);
+            springMvcClientEventListener.onApplicationEvent(event);
+            verify(publisher, times(1)).start(any());
+            verify(publisher, times(2)).publishEvent(any());
+        }
+        registerUtilsMockedStatic.close();
+    }
+
     @Test
     public void testOnBuildApiSuperPath() {
         SpringMvcClientEventListener springMvcClientEventListener = 
buildSpringMvcClientEventListener(false, false);
diff --git 
a/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/main/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListener.java
 
b/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/main/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListener.java
index 338ad8d9a7..ee621e56ab 100644
--- 
a/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/main/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListener.java
+++ 
b/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/main/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListener.java
@@ -21,7 +21,6 @@ import org.apache.commons.lang3.ArrayUtils;
 import org.apache.commons.lang3.StringUtils;
 import 
org.apache.shenyu.client.core.client.AbstractContextRefreshedEventListener;
 import org.apache.shenyu.client.core.constant.ShenyuClientConstants;
-import 
org.apache.shenyu.client.core.disruptor.ShenyuClientRegisterEventPublisher;
 import org.apache.shenyu.client.core.utils.PortUtils;
 import 
org.apache.shenyu.client.spring.websocket.annotation.ShenyuServerEndpoint;
 import 
org.apache.shenyu.client.spring.websocket.annotation.ShenyuSpringWebSocketClient;
@@ -62,8 +61,6 @@ import java.util.Properties;
  */
 public class SpringWebSocketClientEventListener extends 
AbstractContextRefreshedEventListener<Object, ShenyuSpringWebSocketClient> {
     
-    private final ShenyuClientRegisterEventPublisher publisher = 
ShenyuClientRegisterEventPublisher.getInstance();
-    
     private final String[] pathAttributeNames = new String[] {"path", "value"};
 
     private final List<Class<? extends Annotation>> mappingAnnotation = new 
ArrayList<>(7);
@@ -105,9 +102,12 @@ public class SpringWebSocketClientEventListener extends 
AbstractContextRefreshed
     protected Map<String, Object> getBeans(final ApplicationContext context) {
         // Filter out is not controller out
         if (Boolean.TRUE.equals(isFull)) {
+            if (!markRegistered()) {
+                return Collections.emptyMap();
+            }
             LOG.info("init spring websocket client success with isFull mode");
             List<String> namespaceIds = super.getNamespace();
-            namespaceIds.forEach(namespaceId -> 
publisher.publishEvent(buildURIRegisterDTO(context, Collections.emptyMap(), 
namespaceId)));
+            namespaceIds.forEach(namespaceId -> 
getPublisher().publishEvent(buildURIRegisterDTO(context, 
Collections.emptyMap(), namespaceId)));
             return Collections.emptyMap();
         }
         Map<String, Object> endpointBeans = 
context.getBeansWithAnnotation(ShenyuServerEndpoint.class);
diff --git 
a/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/test/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListenerTest.java
 
b/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/test/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListenerTest.java
index 52058efd23..1c5de5420e 100644
--- 
a/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/test/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListenerTest.java
+++ 
b/shenyu-client/shenyu-client-websocket/shenyu-client-spring-websocket/src/test/java/org/apache/shenyu/client/spring/websocket/init/SpringWebSocketClientEventListenerTest.java
@@ -32,9 +32,10 @@ import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockedStatic;
 import org.mockito.junit.jupiter.MockitoExtension;
 import org.springframework.context.ApplicationContext;
+import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.util.ReflectionUtils;
 
 import java.lang.annotation.Annotation;
@@ -49,7 +50,9 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -84,21 +87,7 @@ public class SpringWebSocketClientEventListenerTest {
 
     @BeforeEach
     public void setUp() {
-        MockitoAnnotations.openMocks(this);
-        Properties properties = mock(Properties.class);
-        when(properties.getProperty("appName")).thenReturn("appName");
-        when(properties.getProperty("contextPath")).thenReturn("contextPath");
-        
when(properties.getProperty(ShenyuClientConstants.PORT)).thenReturn("8080");
-        
when(properties.getProperty(ShenyuClientConstants.HOST)).thenReturn("127.0.0.1");
-        
when(properties.getProperty(ShenyuClientConstants.IP_PORT)).thenReturn("127.0.0.1:8080");
-        
-        ShenyuClientConfig clientConfig = mock(ShenyuClientConfig.class);
-        Map<String, ClientPropertiesConfig> client = new HashMap<>();
-        ClientPropertiesConfig clientPropertiesConfig = new 
ClientPropertiesConfig();
-        clientPropertiesConfig.setProps(properties);
-        client.put(RpcTypeEnum.WEB_SOCKET.getName(), clientPropertiesConfig);
-        when(clientConfig.getClient()).thenReturn(client);
-        eventListener = new SpringWebSocketClientEventListener(clientConfig, 
registerRepository);
+        eventListener = buildEventListener(false);
     }
 
     @Test
@@ -178,6 +167,39 @@ public class SpringWebSocketClientEventListenerTest {
         assertEquals(port, "8080");
     }
 
+    @Test
+    public void testOnApplicationEventFullModeShouldRegisterOnce() {
+        try (MockedStatic<ShenyuClientRegisterEventPublisher> 
publisherMockedStatic = mockStatic(ShenyuClientRegisterEventPublisher.class)) {
+            ShenyuClientRegisterEventPublisher mockPublisher = 
mock(ShenyuClientRegisterEventPublisher.class);
+            
publisherMockedStatic.when(ShenyuClientRegisterEventPublisher::getInstance).thenReturn(mockPublisher);
+            SpringWebSocketClientEventListener fullModeEventListener = 
buildEventListener(true);
+            ContextRefreshedEvent event = new 
ContextRefreshedEvent(applicationContext);
+            fullModeEventListener.onApplicationEvent(event);
+            fullModeEventListener.onApplicationEvent(event);
+            verify(mockPublisher, times(1)).start(any());
+            verify(mockPublisher, times(1)).publishEvent(any());
+        }
+    }
+
+    private SpringWebSocketClientEventListener buildEventListener(final 
boolean full) {
+        Properties properties = new Properties();
+        properties.setProperty("appName", "appName");
+        properties.setProperty("contextPath", "contextPath");
+        properties.setProperty(ShenyuClientConstants.PORT, "8080");
+        properties.setProperty(ShenyuClientConstants.HOST, "127.0.0.1");
+        properties.setProperty(ShenyuClientConstants.IP_PORT, 
"127.0.0.1:8080");
+        properties.setProperty(ShenyuClientConstants.IS_FULL, 
String.valueOf(full));
+        properties.setProperty(ShenyuClientConstants.DISCOVERY_LOCAL_MODE_KEY, 
Boolean.TRUE.toString());
+
+        ShenyuClientConfig clientConfig = mock(ShenyuClientConfig.class);
+        Map<String, ClientPropertiesConfig> client = new HashMap<>();
+        ClientPropertiesConfig clientPropertiesConfig = new 
ClientPropertiesConfig();
+        clientPropertiesConfig.setProps(properties);
+        client.put(RpcTypeEnum.WEB_SOCKET.getName(), clientPropertiesConfig);
+        when(clientConfig.getClient()).thenReturn(client);
+        return new SpringWebSocketClientEventListener(clientConfig, 
registerRepository);
+    }
+
     /**
      * class for mock.
      */

Reply via email to