Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -976,8 +976,10 @@ private void buildChildrenPathWithStat(DataNode n, String path, Stat stat, long
if (stat != null) {
n.copyStat(stat);
}
// The root's children are keyed as "/child", not "//child"
final String prefix = "/".equals(path) ? path : path + "/";
for (String child : n.getChildren()) {
DataNode childNode = nodes.get(path + "/" + child);
DataNode childNode = nodes.get(prefix + child);
if (null != childNode) {
final long czxId = childNode.stat.getCzxid();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
Expand Down Expand Up @@ -415,6 +416,57 @@ public void testGetChildrenPaginated() throws NodeExistsException, NoNodeExcepti
assertEquals(0, nextPage.getMinCzxidOffset());
}

@Test(timeout = 60000)
public void testGetChildrenPaginatedOnRoot() throws NodeExistsException, NoNodeException {
final int firstCzxId = 1000;
final int countNodes = 10;

DataTree dt = new DataTree();
// Create the children in reverse lexical order so that ordering by czxid (correct) and
// ordering by name (incorrect) produce different results, and the test can tell them apart.
List<String> childrenCreated = new ArrayList<>(countNodes);
for (int i = 0; i < countNodes; ++i) {
final String name = "test-" + (countNodes - 1 - i);
dt.createNode("/" + name, new byte[0], null, 0, dt.getNode("/").stat.getCversion() + 1, firstCzxId + i, 1);
childrenCreated.add(name);
}

// "/zookeeper" has czxid 0, so it sorts ahead of everything created above.
List<String> expected = new ArrayList<>();
expected.add("zookeeper");
expected.addAll(childrenCreated);

// Bounded pages on "/" must return every child, in creation order
List<String> collected = new ArrayList<>();
PaginationNextPage nextPage = new PaginationNextPage();
long minCzxid = -1;
int offset = 0;
final int watchCount = dt.getWatchCount();
boolean reachedLastPage = false;
for (int i = 0; i < countNodes + 2; ++i) {
List<String> page = dt.getPaginatedChildren("/", null, new DummyWatcher(), 3, minCzxid, offset, nextPage);
collected.addAll(page);
if (nextPage.getMinCzxid() == ZooDefs.GetChildrenPaginated.lastPageMinCzxid) {
reachedLastPage = true;
assertEquals("The watch must be set on the last page", watchCount + 1, dt.getWatchCount());
break;
}
// Asserting this on every intermediate page is what catches the "//child" bug: with the
// bug the very first page looks like a last page and registers the watch immediately.
assertEquals("The watch must not be set before the last page", watchCount, dt.getWatchCount());
minCzxid = nextPage.getMinCzxid();
offset = nextPage.getMinCzxidOffset();
}
assertTrue("Pagination never reported a last page", reachedLastPage);
assertEquals(expected, collected);

// minCzxId > 0 skips the fast path even with an unbounded page size
List<String> result = dt.getPaginatedChildren("/", null, null, Integer.MAX_VALUE, firstCzxId + countNodes - 2, 0, nextPage);
assertEquals(Arrays.asList("test-1", "test-0"), result);
assertEquals("The remainder fits in one page, so it is the last page",
ZooDefs.GetChildrenPaginated.lastPageMinCzxid, nextPage.getMinCzxid());
}

@Test(timeout = 60000)
public void testGetChildrenPaginatedWithOffset() throws NodeExistsException, NoNodeException {
final String rootPath = "/children";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.apache.zookeeper.test;

import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -127,6 +128,38 @@ public void testPaginationIterator() throws Exception {
Assert.assertEquals(createdChildrenMetadata.keySet(), readChildrenMetadata);
}

@Test(timeout = 30000)
public void testPaginationIteratorOnRoot() throws Exception {
final String prefix = "/testPaginationRoot-" + UUID.randomUUID();
List<String> expected = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
zk.create(prefix + i, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
expected.add(prefix.substring(1) + i);
}

// Collect into a List (not a Set) so duplicated or out-of-order pages are caught.
List<String> actual = new ArrayList<String>();
RemoteIterator<String> childrenIterator = zk.getChildrenIterator("/", null, 3, -1);
while (childrenIterator.hasNext()) {
actual.add(childrenIterator.next());
}

Assert.assertEquals("Root pagination returned duplicates: " + actual,
new HashSet<String>(actual).size(), actual.size());
Assert.assertTrue("Missing children of root: " + expected + " vs " + actual, actual.containsAll(expected));
Assert.assertTrue("Missing /zookeeper: " + actual, actual.contains("zookeeper"));

// The nodes were created in order, so they must come back in that order. Other tests may
// leave unrelated nodes at the root, so compare only the ones this test created.
List<String> actualOurs = new ArrayList<String>();
for (String child : actual) {
if (child.startsWith(prefix.substring(1))) {
actualOurs.add(child);
}
}
Assert.assertEquals("Root children returned out of creation order", expected, actualOurs);
}

/*
* This test validates a known list of children is returned by the iterator despite server failures.
* After the iterator is created successfully, the following logic drives the rest of the test:
Expand Down
Loading