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 67596c1d22 fix(eureka): keep instance watch polling after a failed
poll (#7429)
67596c1d22 is described below
commit 67596c1d228b62da75cd0a0ecb47d0ac5d995384
Author: Sean-Walker0 <[email protected]>
AuthorDate: Tue Oct 6 10:36:54 2026 +0800
fix(eureka): keep instance watch polling after a failed poll (#7429)
The periodic watch task in EurekaInstanceRegisterRepository caught
polling errors, logged them, and then rethrew ShenyuException inside
the scheduleAtFixedRate lambda. ScheduledThreadPoolExecutor suppresses
all later executions of a periodic task once an exception escapes it,
so one transient Eureka polling error permanently stopped the watch
and stale instances were never updated again.
Log the failure and return, letting the next scheduled poll retry.
Regression test: the initial fetch succeeds empty, the first poll
throws, the second poll recovers and must still deliver the ADDED
event (red on the previous code, where the task died after the first
failure).
Fixes #6500
Co-authored-by: Sean-Walker0
<[email protected]>
---
.../eureka/EurekaInstanceRegisterRepository.java | 3 +-
.../EurekaInstanceRegisterRepositoryTest.java | 41 ++++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
diff --git
a/shenyu-registry/shenyu-registry-eureka/src/main/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepository.java
b/shenyu-registry/shenyu-registry-eureka/src/main/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepository.java
index 334e7a49bb..33122bc125 100644
---
a/shenyu-registry/shenyu-registry-eureka/src/main/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepository.java
+++
b/shenyu-registry/shenyu-registry-eureka/src/main/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepository.java
@@ -162,8 +162,9 @@ public class EurekaInstanceRegisterRepository implements
ShenyuInstanceRegisterR
compareInstances(new HashSet<>(previousInstances), new
HashSet<>(currentInstances), listener);
instanceListMap.put(key, currentInstances);
} catch (Exception e) {
+ // an exception escaping a fixed-rate task suppresses all
later executions,
+ // so a single polling failure must only be logged to keep
the watch alive
LOGGER.error("eureka registry eurekaDiscoveryService watch
key: {} error", key, e);
- throw new ShenyuException(e);
}
}, 0, 1, TimeUnit.SECONDS);
listenerThreadsMap.put(key, scheduledFuture);
diff --git
a/shenyu-registry/shenyu-registry-eureka/src/test/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepositoryTest.java
b/shenyu-registry/shenyu-registry-eureka/src/test/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepositoryTest.java
index 6e963a1544..b6985af237 100644
---
a/shenyu-registry/shenyu-registry-eureka/src/test/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepositoryTest.java
+++
b/shenyu-registry/shenyu-registry-eureka/src/test/java/org/apache/shenyu/registry/eureka/EurekaInstanceRegisterRepositoryTest.java
@@ -17,26 +17,37 @@
package org.apache.shenyu.registry.eureka;
+import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaEventListener;
import org.apache.shenyu.registry.api.config.RegisterConfig;
import org.apache.shenyu.registry.api.entity.InstanceEntity;
+import org.apache.shenyu.registry.api.event.ChangedEventListener;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
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 EurekaInstanceRegisterRepositoryTest {
@@ -104,6 +115,36 @@ public final class EurekaInstanceRegisterRepositoryTest {
assertTrue(eurekaEventStorage.isEmpty());
}
+ @Test
+ public void testWatchInstancesKeepsPollingAfterFailure() throws Exception {
+ InstanceInfo info = mock(InstanceInfo.class);
+ when(info.getAppName()).thenReturn(instance.getAppName());
+ when(info.getIPAddr()).thenReturn("10.0.0.1");
+ when(info.getPort()).thenReturn(8080);
+ when(info.getInstanceId()).thenReturn("instance-1");
+ when(info.getMetadata()).thenReturn(new HashMap<>());
+
when(info.isPortEnabled(InstanceInfo.PortType.SECURE)).thenReturn(false);
+ when(info.getStatus()).thenReturn(InstanceInfo.InstanceStatus.UP);
+
+ EurekaClient failingThenRecovering = mock(EurekaClient.class);
+
when(failingThenRecovering.getApplicationInfoManager()).thenReturn(mock(ApplicationInfoManager.class));
+
when(failingThenRecovering.getInstancesByVipAddressAndAppName(nullable(String.class),
eq(instance.getAppName()), anyBoolean()))
+ .thenReturn(new ArrayList<>())
+ .thenThrow(new RuntimeException("eureka temporarily
unavailable"))
+ .thenReturn(Collections.singletonList(info));
+
+ Field eurekaClientField =
repository.getClass().getDeclaredField("eurekaClient");
+ eurekaClientField.setAccessible(true);
+ eurekaClientField.set(repository, failingThenRecovering);
+
+ ChangedEventListener listener = mock(ChangedEventListener.class);
+ repository.watchInstances(instance.getAppName(), listener);
+
+ verify(listener, timeout(5000).atLeastOnce())
+ .onEvent(eq(instance.getAppName()), anyString(),
eq(ChangedEventListener.Event.ADDED));
+ repository.close();
+ }
+
@AfterEach
public void clear() {
discoveryClientMockedConstruction.close();