Skip to content

Commit 8a8f4d6

Browse files
authored
[FIX] fix SyncLedgerIterator.hasNext() failing to iterate across ZK ledger ranges (#4731)
1 parent 8664dd9 commit 8a8f4d6

2 files changed

Lines changed: 156 additions & 7 deletions

File tree

bookkeeper-server/src/main/java/org/apache/bookkeeper/client/BookKeeper.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,17 +1583,20 @@ public SyncLedgerIterator(LedgerRangeIterator iterator, ListLedgersResultImpl pa
15831583
this.parent = parent;
15841584
}
15851585

1586-
@Override
1586+
/**
1587+
* ZooKeeper stores ledgers in a hierarchical tree (e.g. /ledgers/00/0000/...).
1588+
* {@code iterator} (LedgerRangeIterator) traverses the intermediate tree nodes (ranges/buckets),
1589+
* while {@code currentRange} iterates over the leaf-level ledger IDs within a single range.
1590+
*
1591+
* Therefore, when {@code currentRange} has no more leaf nodes, we need to check
1592+
* {@code iterator.hasNext()} to determine if there are more ranges to advance to.
1593+
*/
15871594
public boolean hasNext() throws IOException {
15881595
parent.checkClosed();
1589-
if (currentRange != null) {
1590-
if (currentRange.hasNext()) {
1591-
return true;
1592-
}
1593-
} else if (iterator.hasNext()) {
1596+
if (currentRange != null && currentRange.hasNext()) {
15941597
return true;
15951598
}
1596-
return false;
1599+
return iterator.hasNext();
15971600
}
15981601

15991602
@Override

bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/LedgerMetadataTest.java

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,150 @@ public void testListLedgers()
190190
}
191191
}
192192

193+
/**
194+
* Unlike {@link #testListLedgers()} which only uses 10 ledgers within a single ZK range,
195+
* this test creates 10010 ledgers to verify cross-range iteration.
196+
*
197+
* <p>The HierarchicalLedgerManager stores ledgers in a ZK tree where each range (znode)
198+
* holds at most 10,000 leaf nodes. This limit is determined by the 4-digit last level of
199+
* the ledger ID path (0000-9999), as defined in:
200+
* <ul>
201+
* <li>{@code LegacyHierarchicalLedgerManager} — 2-4-4 split,
202+
* path: {root}/{level1}/{level2}/L{level3}</li>
203+
* <li>{@code LongHierarchicalLedgerManager} — 3-4-4-4-4 split,
204+
* path: {root}/{level0}/{level1}/{level2}/{level3}/L{level4}</li>
205+
* </ul>
206+
*
207+
* <p>By using numOfLedgers = 10010 (> 10000), ledgers will span across multiple ranges,
208+
* exercising the SyncLedgerIterator's ability to advance from one exhausted range to the
209+
* next via LedgerRangeIterator.
210+
*/
211+
@Test
212+
public void testListLedgersLargeScale()
213+
throws Exception {
214+
// 10010 > 10000 (max ledgers per ZK range, determined by 4-digit last level 0000-9999),
215+
// ensuring cross-range iteration is covered
216+
int numOfLedgers = 10010;
217+
218+
ClientConfiguration conf = new ClientConfiguration();
219+
conf.setMetadataServiceUri(zkUtil.getMetadataServiceUri());
220+
221+
try (BookKeeper bkc = BookKeeper.newBuilder(conf).build();) {
222+
long[] ledgerIds = new long[numOfLedgers];
223+
for (int i = 0; i < numOfLedgers; i++) {
224+
225+
try (WriteHandle l = bkc
226+
.newCreateLedgerOp()
227+
.withDigestType(DigestType.CRC32)
228+
.withPassword("testPasswd".getBytes())
229+
.execute()
230+
.get();) {
231+
ledgerIds[i] = l.getId();
232+
}
233+
}
234+
235+
try (ListLedgersResult result = FutureUtils.result(bkc.newListLedgersOp().execute());) {
236+
int count = 0;
237+
238+
for (long ledgerId : result.toIterable()) {
239+
assertEquals(ledgerIds[count++], ledgerId);
240+
}
241+
242+
assertEquals(numOfLedgers, count, "Unexpected ledgers count");
243+
try {
244+
result.iterator();
245+
fail("Should thrown error");
246+
} catch (IllegalStateException e) {
247+
// ok
248+
}
249+
try {
250+
result.toIterable();
251+
fail("Should thrown error");
252+
} catch (IllegalStateException e) {
253+
// ok
254+
}
255+
}
256+
257+
try (ListLedgersResult result = FutureUtils.result(bkc.newListLedgersOp().execute());) {
258+
int count = 0;
259+
260+
for (LedgersIterator iterator = result.iterator(); iterator.hasNext();) {
261+
long ledgerId = iterator.next();
262+
assertEquals(ledgerIds[count++], ledgerId);
263+
264+
}
265+
assertEquals(numOfLedgers, count, "Unexpected ledgers count");
266+
try {
267+
result.iterator();
268+
fail("Should thrown error");
269+
} catch (IllegalStateException e) {
270+
// ok
271+
}
272+
try {
273+
result.toIterable();
274+
fail("Should thrown error");
275+
} catch (IllegalStateException e) {
276+
// ok
277+
}
278+
}
279+
}
280+
281+
// check closed
282+
{
283+
ListLedgersResult result = FutureUtils.result(bkc.newListLedgersOp().execute());
284+
result.close();
285+
try {
286+
result.toIterable();
287+
fail("Should thrown error");
288+
} catch (IllegalStateException e) {
289+
// ok
290+
}
291+
292+
try {
293+
result.iterator();
294+
fail("Should thrown error");
295+
} catch (IllegalStateException e) {
296+
// ok
297+
}
298+
}
299+
300+
{ // iterator
301+
ListLedgersResult result = FutureUtils.result(bkc.newListLedgersOp().execute());
302+
LedgersIterator it = result.iterator();
303+
result.close();
304+
try {
305+
it.hasNext();
306+
fail("Should thrown error");
307+
} catch (IllegalStateException e) {
308+
// ok
309+
}
310+
311+
try {
312+
it.next();
313+
fail("Should thrown error");
314+
} catch (IllegalStateException e) {
315+
// ok
316+
}
317+
}
318+
319+
{ // iterable
320+
ListLedgersResult result = FutureUtils.result(bkc.newListLedgersOp().execute());
321+
Iterator<Long> it = result.toIterable().iterator();
322+
result.close();
323+
try {
324+
it.hasNext();
325+
fail("Should thrown error");
326+
} catch (IllegalStateException e) {
327+
// ok
328+
}
329+
330+
try {
331+
it.next();
332+
fail("Should thrown error");
333+
} catch (IllegalStateException e) {
334+
// ok
335+
}
336+
}
337+
}
338+
193339
}

0 commit comments

Comments
 (0)