From ff4750bcc8f8315c966a42004012c9042540a787 Mon Sep 17 00:00:00 2001 From: Kezhu Wang Date: Tue, 22 Feb 2022 19:17:05 +0800 Subject: [PATCH] ZOOKEEPER-4475: Fix NodeChildrenChanged delivered to recursive watcher The semantics of persistent recursive watch promise no child events on descendant nodes. When there are standard child watches on descendants of node being watches in persistent recursive mode, server will deliver child events to client inevitably. So we have to filter out child events for persistent recursive watches on client side. --- .../org/apache/zookeeper/ZKWatchManager.java | 15 +++++++++---- .../test/PersistentRecursiveWatcherTest.java | 21 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKWatchManager.java b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKWatchManager.java index 95a07f0e7be..9da4249447a 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/ZKWatchManager.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/ZKWatchManager.java @@ -404,13 +404,13 @@ public Set materialize( synchronized (existWatches) { addTo(existWatches.remove(clientPath), result); } - addPersistentWatches(clientPath, result); + addPersistentWatches(clientPath, type, result); break; case NodeChildrenChanged: synchronized (childWatches) { addTo(childWatches.remove(clientPath), result); } - addPersistentWatches(clientPath, result); + addPersistentWatches(clientPath, type, result); break; case NodeDeleted: synchronized (dataWatches) { @@ -427,7 +427,7 @@ public Set materialize( synchronized (childWatches) { addTo(childWatches.remove(clientPath), result); } - addPersistentWatches(clientPath, result); + addPersistentWatches(clientPath, type, result); break; default: String errorMsg = String.format( @@ -442,10 +442,17 @@ public Set materialize( return result; } - private void addPersistentWatches(String clientPath, Set result) { + private void addPersistentWatches(String clientPath, Watcher.Event.EventType type, Set result) { synchronized (persistentWatches) { addTo(persistentWatches.get(clientPath), result); } + // The semantics of persistent recursive watch promise no child events on descendant nodes. When there + // are standard child watches on descendants of node being watched in persistent recursive mode, server + // will deliver child events to client inevitably. So we have to filter out child events for persistent + // recursive watches on client side. + if (type == Watcher.Event.EventType.NodeChildrenChanged) { + return; + } synchronized (persistentRecursiveWatches) { for (String path : PathParentIterator.forAll(clientPath).asIterable()) { addTo(persistentRecursiveWatches.get(path), result); diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentRecursiveWatcherTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentRecursiveWatcherTest.java index 80d8c400cd2..e74ee2fd683 100644 --- a/zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentRecursiveWatcherTest.java +++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentRecursiveWatcherTest.java @@ -114,6 +114,27 @@ public void testRemoval() } } + @Test + public void testNoChildEvents() throws Exception { + try (ZooKeeper zk = createClient()) { + zk.create("/a", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + zk.addWatch("/", persistentWatcher, PERSISTENT_RECURSIVE); + + BlockingQueue childEvents = new LinkedBlockingQueue<>(); + zk.getChildren("/a", childEvents::add); + + zk.create("/a/b", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + zk.create("/a/b/c", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); + + assertEvent(childEvents, Watcher.Event.EventType.NodeChildrenChanged, "/a"); + + assertEvent(events, Watcher.Event.EventType.NodeCreated, "/a/b"); + assertEvent(events, Watcher.Event.EventType.NodeCreated, "/a/b/c"); + assertTrue(events.isEmpty()); + } + } + @Test public void testDisconnect() throws Exception { try (ZooKeeper zk = createClient(new CountdownWatcher(), hostPort)) {