This is an automated email from the ASF dual-hosted git repository.

shenwenbing pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 8a8f4d607d [FIX] fix SyncLedgerIterator.hasNext() failing to iterate 
across ZK ledger ranges (#4731)
8a8f4d607d is described below

commit 8a8f4d607dc7056a75178316dc563445165ad460
Author: zhou zhuohan <[email protected]>
AuthorDate: Tue Mar 24 11:59:32 2026 +0800

    [FIX] fix SyncLedgerIterator.hasNext() failing to iterate across ZK ledger 
ranges (#4731)
---
 .../org/apache/bookkeeper/client/BookKeeper.java   |  17 ++-
 .../bookkeeper/client/api/LedgerMetadataTest.java  | 146 +++++++++++++++++++++
 2 files changed, 156 insertions(+), 7 deletions(-)

diff --git 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
index 0c7dbf7836..9c70f58298 100644
--- 
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
+++ 
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java
@@ -1583,17 +1583,20 @@ public class BookKeeper implements 
org.apache.bookkeeper.client.api.BookKeeper {
             this.parent = parent;
         }
 
-        @Override
+        /**
+         * ZooKeeper stores ledgers in a hierarchical tree (e.g. 
/ledgers/00/0000/...).
+         * {@code iterator} (LedgerRangeIterator) traverses the intermediate 
tree nodes (ranges/buckets),
+         * while {@code currentRange} iterates over the leaf-level ledger IDs 
within a single range.
+         *
+         * Therefore, when {@code currentRange} has no more leaf nodes, we 
need to check
+         * {@code iterator.hasNext()} to determine if there are more ranges to 
advance to.
+         */
         public boolean hasNext() throws IOException {
             parent.checkClosed();
-            if (currentRange != null) {
-                if (currentRange.hasNext()) {
-                    return true;
-                }
-            } else if (iterator.hasNext()) {
+            if (currentRange != null && currentRange.hasNext()) {
                 return true;
             }
-            return false;
+            return iterator.hasNext();
         }
 
         @Override
diff --git 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/LedgerMetadataTest.java
 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/LedgerMetadataTest.java
index d7c4c9ba8f..d4bef13528 100644
--- 
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/LedgerMetadataTest.java
+++ 
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/LedgerMetadataTest.java
@@ -190,4 +190,150 @@ public class LedgerMetadataTest extends 
BookKeeperClusterTestCase {
         }
     }
 
+    /**
+     * Unlike {@link #testListLedgers()} which only uses 10 ledgers within a 
single ZK range,
+     * this test creates 10010 ledgers to verify cross-range iteration.
+     *
+     * <p>The HierarchicalLedgerManager stores ledgers in a ZK tree where each 
range (znode)
+     * holds at most 10,000 leaf nodes. This limit is determined by the 
4-digit last level of
+     * the ledger ID path (0000-9999), as defined in:
+     * <ul>
+     *   <li>{@code LegacyHierarchicalLedgerManager} — 2-4-4 split,
+     *       path: {root}/{level1}/{level2}/L{level3}</li>
+     *   <li>{@code LongHierarchicalLedgerManager} — 3-4-4-4-4 split,
+     *       path: {root}/{level0}/{level1}/{level2}/{level3}/L{level4}</li>
+     * </ul>
+     *
+     * <p>By using numOfLedgers = 10010 (> 10000), ledgers will span across 
multiple ranges,
+     * exercising the SyncLedgerIterator's ability to advance from one 
exhausted range to the
+     * next via LedgerRangeIterator.
+     */
+    @Test
+    public void testListLedgersLargeScale()
+            throws Exception {
+        // 10010 > 10000 (max ledgers per ZK range, determined by 4-digit last 
level 0000-9999),
+        // ensuring cross-range iteration is covered
+        int numOfLedgers = 10010;
+
+        ClientConfiguration conf = new ClientConfiguration();
+        conf.setMetadataServiceUri(zkUtil.getMetadataServiceUri());
+
+        try (BookKeeper bkc = BookKeeper.newBuilder(conf).build();) {
+            long[] ledgerIds = new long[numOfLedgers];
+            for (int i = 0; i < numOfLedgers; i++) {
+
+                try (WriteHandle l = bkc
+                        .newCreateLedgerOp()
+                        .withDigestType(DigestType.CRC32)
+                        .withPassword("testPasswd".getBytes())
+                        .execute()
+                        .get();) {
+                    ledgerIds[i] = l.getId();
+                }
+            }
+
+            try (ListLedgersResult result = 
FutureUtils.result(bkc.newListLedgersOp().execute());) {
+                int count = 0;
+
+                for (long ledgerId : result.toIterable()) {
+                    assertEquals(ledgerIds[count++], ledgerId);
+                }
+
+                assertEquals(numOfLedgers, count, "Unexpected ledgers count");
+                try {
+                    result.iterator();
+                    fail("Should thrown error");
+                } catch (IllegalStateException e) {
+                    // ok
+                }
+                try {
+                    result.toIterable();
+                    fail("Should thrown error");
+                } catch (IllegalStateException e) {
+                    // ok
+                }
+            }
+
+            try (ListLedgersResult result = 
FutureUtils.result(bkc.newListLedgersOp().execute());) {
+                int count = 0;
+
+                for (LedgersIterator iterator = result.iterator(); 
iterator.hasNext();) {
+                    long ledgerId = iterator.next();
+                    assertEquals(ledgerIds[count++], ledgerId);
+
+                }
+                assertEquals(numOfLedgers, count, "Unexpected ledgers count");
+                try {
+                    result.iterator();
+                    fail("Should thrown error");
+                } catch (IllegalStateException e) {
+                    // ok
+                }
+                try {
+                    result.toIterable();
+                    fail("Should thrown error");
+                } catch (IllegalStateException e) {
+                    // ok
+                }
+            }
+        }
+
+        // check closed
+        {
+            ListLedgersResult result = 
FutureUtils.result(bkc.newListLedgersOp().execute());
+            result.close();
+            try {
+                result.toIterable();
+                fail("Should thrown error");
+            } catch (IllegalStateException e) {
+                // ok
+            }
+
+            try {
+                result.iterator();
+                fail("Should thrown error");
+            } catch (IllegalStateException e) {
+                // ok
+            }
+        }
+
+        { // iterator
+            ListLedgersResult result = 
FutureUtils.result(bkc.newListLedgersOp().execute());
+            LedgersIterator it = result.iterator();
+            result.close();
+            try {
+                it.hasNext();
+                fail("Should thrown error");
+            } catch (IllegalStateException e) {
+                // ok
+            }
+
+            try {
+                it.next();
+                fail("Should thrown error");
+            } catch (IllegalStateException e) {
+                // ok
+            }
+        }
+
+        { // iterable
+            ListLedgersResult result = 
FutureUtils.result(bkc.newListLedgersOp().execute());
+            Iterator<Long> it = result.toIterable().iterator();
+            result.close();
+            try {
+                it.hasNext();
+                fail("Should thrown error");
+            } catch (IllegalStateException e) {
+                // ok
+            }
+
+            try {
+                it.next();
+                fail("Should thrown error");
+            } catch (IllegalStateException e) {
+                // ok
+            }
+        }
+    }
+
 }

Reply via email to