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 9a93ecc531 fix(consul): keep instance watch polling after a failed 
query (#7433)
9a93ecc531 is described below

commit 9a93ecc5317cabdfa332a37e8f1441c24ae4b149
Author: Sean-Walker0 <[email protected]>
AuthorDate: Thu Oct 8 14:46:30 2026 +0800

    fix(consul): keep instance watch polling after a failed query (#7433)
    
    watcherStart scheduled watchConfigKeyValues directly with
    scheduleWithFixedDelay and no exception guard. getHealthServices
    performs the Consul blocking query and rethrows errors, and
    ScheduledThreadPoolExecutor suppresses all later executions of a
    periodic task once an exception escapes it, so one transient Consul
    error permanently stopped the watch and stale instances were served
    from watcherInstanceRegisterMap until process restart.
    
    Catch the failure inside the scheduled task, log it, and let the next
    scheduled poll retry. Regression test: the first getHealthServices
    call throws, the second succeeds, and the watch must poll at least
    twice (red on the previous code, where the task died after the first
    failure).
    
    Fixes #6499
    
    Co-authored-by: Sean-Walker0 
<[email protected]>
---
 .../consul/ConsulInstanceRegisterRepository.java   | 11 ++++++--
 .../ConsulInstanceRegisterRepositoryTest.java      | 30 ++++++++++++++++++++++
 2 files changed, 39 insertions(+), 2 deletions(-)

diff --git 
a/shenyu-registry/shenyu-registry-consul/src/main/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepository.java
 
b/shenyu-registry/shenyu-registry-consul/src/main/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepository.java
index af5c2785cc..b19a4d626d 100644
--- 
a/shenyu-registry/shenyu-registry-consul/src/main/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepository.java
+++ 
b/shenyu-registry/shenyu-registry-consul/src/main/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepository.java
@@ -183,8 +183,15 @@ public class ConsulInstanceRegisterRepository implements 
ShenyuInstanceRegisterR
         if (!watchSelectKeySet.add(selectKey)) {
             return;
         }
-        watchFutures.add(this.executor.scheduleWithFixedDelay(() -> 
this.watchConfigKeyValues(selectKey),
-                5, Integer.parseInt(watchDelay), TimeUnit.SECONDS));
+        watchFutures.add(this.executor.scheduleWithFixedDelay(() -> {
+            try {
+                this.watchConfigKeyValues(selectKey);
+            } catch (Exception e) {
+                // an exception escaping a fixed-delay task suppresses all 
later executions,
+                // so a single watch failure must only be logged to keep 
watching
+                LOGGER.error("consul registry watch key: {} error", selectKey, 
e);
+            }
+        }, 5, Integer.parseInt(watchDelay), TimeUnit.SECONDS));
     }
 
     /**
diff --git 
a/shenyu-registry/shenyu-registry-consul/src/test/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepositoryTest.java
 
b/shenyu-registry/shenyu-registry-consul/src/test/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepositoryTest.java
index 8d5bb3df2e..56f9d4f979 100644
--- 
a/shenyu-registry/shenyu-registry-consul/src/test/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepositoryTest.java
+++ 
b/shenyu-registry/shenyu-registry-consul/src/test/java/org/apache/shenyu/registry/consul/ConsulInstanceRegisterRepositoryTest.java
@@ -18,7 +18,9 @@
 package org.apache.shenyu.registry.consul;
 
 import com.ecwid.consul.v1.ConsulClient;
+import com.ecwid.consul.v1.Response;
 import com.ecwid.consul.v1.agent.model.NewService;
+import com.ecwid.consul.v1.health.HealthServicesRequest;
 import org.apache.shenyu.registry.api.config.RegisterConfig;
 import org.apache.shenyu.registry.api.entity.InstanceEntity;
 import org.apache.shenyu.registry.api.path.InstancePathConstants;
@@ -27,6 +29,7 @@ import org.junit.jupiter.api.Test;
 import org.mockito.MockedConstruction;
 
 import java.lang.reflect.Field;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
@@ -35,12 +38,15 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.junit.jupiter.api.Assertions.assertAll;
 import static org.junit.jupiter.api.Assertions.assertFalse;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 public final class ConsulInstanceRegisterRepositoryTest {
@@ -160,4 +166,28 @@ public final class ConsulInstanceRegisterRepositoryTest {
             ttlExecutor.shutdownNow();
         }
     }
+
+    @Test
+    public void testWatcherKeepsPollingAfterFailure() throws 
NoSuchFieldException, IllegalAccessException {
+        final ConsulClient consulClient = mock(ConsulClient.class);
+        when(consulClient.getHealthServices(anyString(), 
any(HealthServicesRequest.class)))
+                .thenThrow(new RuntimeException("consul temporarily 
unavailable"))
+                .thenReturn(new Response<>(Collections.emptyList(), 1L, 
Boolean.TRUE, 0L));
+
+        final Field consulClientField = 
ConsulInstanceRegisterRepository.class.getDeclaredField("consulClient");
+        consulClientField.setAccessible(true);
+        consulClientField.set(repository, consulClient);
+        final Field watchDelayField = 
ConsulInstanceRegisterRepository.class.getDeclaredField("watchDelay");
+        watchDelayField.setAccessible(true);
+        watchDelayField.set(repository, "1");
+        final Field waitTimeField = 
ConsulInstanceRegisterRepository.class.getDeclaredField("waitTime");
+        waitTimeField.setAccessible(true);
+        waitTimeField.set(repository, "0");
+
+        repository.watcherStart("test-service");
+
+        verify(consulClient, timeout(15000).atLeast(2))
+                .getHealthServices(eq("test-service"), 
any(HealthServicesRequest.class));
+        repository.close();
+    }
 }

Reply via email to