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 21e3c65ee3 fix(plugin-grpc): preserve upstreams on unchanged resolver
notifications. (#7747)
21e3c65ee3 is described below
commit 21e3c65ee31f835af8824acae606c1893b58c80d
Author: ocean <[email protected]>
AuthorDate: Mon Oct 19 14:01:41 2026 +0800
fix(plugin-grpc): preserve upstreams on unchanged resolver notifications.
(#7747)
---
.../plugin/grpc/resolver/ShenyuNameResolver.java | 2 +-
.../grpc/resolver/ShenyuNameResolverTest.java | 143 +++++++++++++++++++++
2 files changed, 144 insertions(+), 1 deletion(-)
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/resolver/ShenyuNameResolver.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/resolver/ShenyuNameResolver.java
index 7784c2f877..890cc35f5d 100644
---
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/resolver/ShenyuNameResolver.java
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/main/java/org/apache/shenyu/plugin/grpc/resolver/ShenyuNameResolver.java
@@ -165,7 +165,7 @@ public class ShenyuNameResolver extends NameResolver
implements Consumer<Object>
if (!needsToUpdateConnections(grpcUpstreamList)) {
LOG.info("Nothing has changed... skipping update for {}",
name);
- return Collections.emptyList();
+ return this.savedInstanceList;
}
LOG.info("Ready to update server list for {}", name);
diff --git
a/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/resolver/ShenyuNameResolverTest.java
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/resolver/ShenyuNameResolverTest.java
new file mode 100644
index 0000000000..141da27660
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-proxy/shenyu-plugin-rpc/shenyu-plugin-grpc/src/test/java/org/apache/shenyu/plugin/grpc/resolver/ShenyuNameResolverTest.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.shenyu.plugin.grpc.resolver;
+
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import io.grpc.SynchronizationContext;
+import io.grpc.internal.SharedResourceHolder;
+import org.apache.shenyu.common.dto.convert.selector.GrpcUpstream;
+import org.apache.shenyu.plugin.grpc.cache.ApplicationConfigCache;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.concurrent.Executor;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for {@link ShenyuNameResolver} upstream notifications.
+ */
+public class ShenyuNameResolverTest {
+
+ private static final String APP_NAME = "resolver-notification-test";
+
+ private ApplicationConfigCache cache;
+
+ private ShenyuNameResolver resolver;
+
+ private NameResolver.Listener2 listener;
+
+ @BeforeEach
+ @SuppressWarnings("unchecked")
+ public void setUp() {
+ cache = ApplicationConfigCache.getInstance();
+ cache.invalidate(APP_NAME);
+ NameResolver.Args args = mock(NameResolver.Args.class);
+ when(args.getSynchronizationContext()).thenReturn(new
SynchronizationContext((thread, error) -> {
+ throw new AssertionError(error);
+ }));
+ SharedResourceHolder.Resource<Executor> resource =
mock(SharedResourceHolder.Resource.class);
+ when(resource.create()).thenReturn(Runnable::run);
+ resolver = new ShenyuNameResolver(APP_NAME, args, resource);
+ listener = mock(NameResolver.Listener2.class);
+ }
+
+ @AfterEach
+ public void tearDown() {
+ resolver.shutdown();
+ cache.invalidate(APP_NAME);
+ }
+
+ @Test
+ public void testRepeatedUnchangedNotificationsDoNotResolveAgain() {
+ updateUpstream("127.0.0.1:8080");
+ resolver.start(listener);
+
+ updateUpstream("127.0.0.1:8080");
+ updateUpstream("127.0.0.1:8080");
+ resolver.refresh();
+
+ verify(listener,
times(1)).onResult(any(NameResolver.ResolutionResult.class));
+ verify(listener, never()).onError(any(Status.class));
+ }
+
+ @Test
+ public void testChangedUpstreamResolvesAfterUnchangedNotification() {
+ updateUpstream("127.0.0.1:8080");
+ resolver.start(listener);
+
+ updateUpstream("127.0.0.1:8080");
+ updateUpstream("127.0.0.1:8081");
+ updateUpstream("127.0.0.1:8081");
+ updateUpstream("127.0.0.1:8081");
+
+ ArgumentCaptor<NameResolver.ResolutionResult> results =
ArgumentCaptor.forClass(NameResolver.ResolutionResult.class);
+ verify(listener, times(2)).onResult(results.capture());
+ assertEquals(new InetSocketAddress("127.0.0.1", 8081),
results.getValue().getAddresses().get(0).getAddresses().get(0));
+ verify(listener, never()).onError(any(Status.class));
+ }
+
+ @Test
+ public void testEmptyUpstreamsReportErrorAndCanRecover() {
+ updateUpstream("127.0.0.1:8080");
+ resolver.start(listener);
+ updateUpstream("127.0.0.1:8080");
+
+ cache.invalidate(APP_NAME);
+ resolver.refresh();
+ updateUpstream("127.0.0.1:8080");
+ resolver.refresh();
+
+ verify(listener,
times(2)).onResult(any(NameResolver.ResolutionResult.class));
+ ArgumentCaptor<Status> error = ArgumentCaptor.forClass(Status.class);
+ verify(listener).onError(error.capture());
+ assertEquals(Status.Code.UNAVAILABLE, error.getValue().getCode());
+ assertEquals("No servers found for " + APP_NAME,
error.getValue().getDescription());
+ }
+
+ @Test
+ public void testInvalidUpstreamReportsErrorAndCanRecover() {
+ updateUpstream("127.0.0.1:8080");
+ resolver.start(listener);
+
+ updateUpstream("127.0.0.1:invalid");
+ updateUpstream("127.0.0.1:8080");
+
+ verify(listener,
times(2)).onResult(any(NameResolver.ResolutionResult.class));
+ ArgumentCaptor<Status> error = ArgumentCaptor.forClass(Status.class);
+ verify(listener).onError(error.capture());
+ assertEquals(Status.Code.UNAVAILABLE, error.getValue().getCode());
+ assertEquals("Failed to update server list for " + APP_NAME,
error.getValue().getDescription());
+ }
+
+ private void updateUpstream(final String url) {
+ cache.handlerUpstream(APP_NAME,
Collections.singletonList(GrpcUpstream.builder()
+ .upstreamUrl(url).weight(50).status(true).build()));
+ }
+}