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 6db6b391ca fix(zookeeper): read the deleted node from oldData in
instance watchers (#7326)
6db6b391ca is described below
commit 6db6b391ca684ecca8c80a2468947b55f81d0a9c
Author: Sean-Walker0 <[email protected]>
AuthorDate: Sun Sep 27 12:44:50 2026 +0800
fix(zookeeper): read the deleted node from oldData in instance watchers
(#7326)
CuratorCacheListener delivers NODE_DELETED with a null new ChildData
and the deleted node in oldData. The watcher's first guard bailed out
whenever data was null, before the switch, so the NODE_DELETED branch
was unreachable: instance-removal events were never surfaced and admin
kept stale upstreams for Zookeeper-registered services until a full
resync. Etcd's sibling repository handles its DELETE from the event key
the same way this change now does for Zookeeper.
Use oldData as the changed node for NODE_DELETED (and data otherwise),
keeping the null guards for safety. The new test fires a
NODE_DELETED event through a captured listener and fails on current
master (zero interactions) and passes with this change.
Co-authored-by: Sean-Walker0
<[email protected]>
Co-authored-by: aias00 <[email protected]>
---
.../ZookeeperInstanceRegisterRepository.java | 12 ++++++----
.../ZookeeperInstanceRegisterRepositoryTest.java | 28 ++++++++++++++++++++++
2 files changed, 36 insertions(+), 4 deletions(-)
diff --git
a/shenyu-registry/shenyu-registry-zookeeper/src/main/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepository.java
b/shenyu-registry/shenyu-registry-zookeeper/src/main/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepository.java
index 4404c440b8..86edd43333 100644
---
a/shenyu-registry/shenyu-registry-zookeeper/src/main/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepository.java
+++
b/shenyu-registry/shenyu-registry-zookeeper/src/main/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepository.java
@@ -22,7 +22,9 @@ import com.google.common.collect.Multimap;
import com.google.gson.JsonObject;
import org.apache.commons.lang3.StringUtils;
import org.apache.curator.framework.api.CuratorWatcher;
+import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.CuratorCache;
+import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.shenyu.common.constant.Constants;
import org.apache.shenyu.common.exception.ShenyuException;
@@ -186,13 +188,15 @@ public class ZookeeperInstanceRegisterRepository
implements ShenyuInstanceRegist
public void watchInstances(final String key, final ChangedEventListener
listener) {
try {
CuratorCache treeCache = client.addCache(key, (type, oldData,
data) -> {
- if (!Objects.nonNull(data) ||
!Objects.nonNull(data.getData())) {
+ // Curator delivers NODE_DELETED with a null new ChildData and
the deleted node in oldData
+ ChildData changedNode = CuratorCacheListener.Type.NODE_DELETED
== type ? oldData : data;
+ if (!Objects.nonNull(changedNode) ||
!Objects.nonNull(changedNode.getData())) {
return;
}
- String currentPath = data.getPath();
- String currentData = new String(data.getData(),
StandardCharsets.UTF_8);
+ String currentPath = changedNode.getPath();
+ String currentData = new String(changedNode.getData(),
StandardCharsets.UTF_8);
LOGGER.info("zookeeper registry watch find resultData ={}",
currentData);
- Stat stat = data.getStat();
+ Stat stat = changedNode.getStat();
boolean isEphemeral = Objects.nonNull(stat) &&
stat.getEphemeralOwner() > 0;
if (!isEphemeral) {
LOGGER.info("zookeeper registry watch Ignore non-ephemeral
node changes path {}", currentPath);
diff --git
a/shenyu-registry/shenyu-registry-zookeeper/src/test/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepositoryTest.java
b/shenyu-registry/shenyu-registry-zookeeper/src/test/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepositoryTest.java
index ad6838983b..9fda0407f5 100644
---
a/shenyu-registry/shenyu-registry-zookeeper/src/test/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepositoryTest.java
+++
b/shenyu-registry/shenyu-registry-zookeeper/src/test/java/org/apache/shenyu/registry/zookeeper/ZookeeperInstanceRegisterRepositoryTest.java
@@ -19,10 +19,13 @@ package org.apache.shenyu.registry.zookeeper;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorWatcher;
+import org.apache.curator.framework.recipes.cache.ChildData;
+import org.apache.curator.framework.recipes.cache.CuratorCacheListener;
import org.apache.curator.framework.listen.Listenable;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.registry.api.event.ChangedEventListener;
import org.apache.shenyu.infra.zookeeper.client.ZookeeperClient;
import org.apache.shenyu.registry.api.config.RegisterConfig;
import org.apache.shenyu.registry.api.entity.InstanceEntity;
@@ -105,4 +108,29 @@ public final class ZookeeperInstanceRegisterRepositoryTest
{
}
}
+ @Test
+ public void testWatchInstancesEmitsDeletedEventForEphemeralNode() {
+ final Listenable listenable = mock(Listenable.class);
+ try (MockedConstruction<ZookeeperClient> construction =
mockConstruction(ZookeeperClient.class, (mock, context) -> {
+ final CuratorFramework curatorFramework =
mock(CuratorFramework.class);
+ when(mock.getClient()).thenReturn(curatorFramework);
+
when(curatorFramework.getConnectionStateListenable()).thenReturn(listenable);
+ })) {
+ final ZookeeperInstanceRegisterRepository repository = new
ZookeeperInstanceRegisterRepository();
+ RegisterConfig config = new RegisterConfig();
+ repository.init(config);
+ ZookeeperClient client = construction.constructed().get(0);
+ org.mockito.ArgumentCaptor<CuratorCacheListener> captor =
org.mockito.ArgumentCaptor.forClass(CuratorCacheListener.class);
+ ChangedEventListener changedEventListener =
mock(ChangedEventListener.class);
+ repository.watchInstances("/shenyu/register/instance",
changedEventListener);
+
org.mockito.Mockito.verify(client).addCache(org.mockito.ArgumentMatchers.eq("/shenyu/register/instance"),
captor.capture());
+ org.apache.zookeeper.data.Stat stat = new
org.apache.zookeeper.data.Stat();
+ stat.setEphemeralOwner(1L);
+ ChildData deletedNode = new
ChildData("/shenyu/register/instance/app-host-9195", stat,
"instance-data".getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ // Curator delivers NODE_DELETED with a null new ChildData and the
node in oldData
+ captor.getValue().event(CuratorCacheListener.Type.NODE_DELETED,
deletedNode, null);
+
org.mockito.Mockito.verify(changedEventListener).onEvent("/shenyu/register/instance/app-host-9195",
"instance-data", ChangedEventListener.Event.DELETED);
+ }
+ }
+
}