This is an automated email from the ASF dual-hosted git repository.
merlimat 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 58c8503562 [[BP-62] Expose batchReadUnconfirmedAsync to ReadHandle
(#4739)
58c8503562 is described below
commit 58c850356271360dc9030ca85cf3f61429a8faaa
Author: 道君- Tao Jiuming <[email protected]>
AuthorDate: Fri May 8 05:16:40 2026 +0800
[[BP-62] Expose batchReadUnconfirmedAsync to ReadHandle (#4739)
* Expose batchReadUnconfirmedAsync to ReadHandle.java
* Address review comments
---
.../org/apache/bookkeeper/client/LedgerHandle.java | 59 ++++++++
.../apache/bookkeeper/client/api/ReadHandle.java | 16 +++
.../bookkeeper/client/MockBookKeeperTestCase.java | 80 +++++++++++
.../bookkeeper/client/api/BookKeeperApiTest.java | 148 +++++++++++++++++++++
4 files changed, 303 insertions(+)
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
index d129fb9f6e..e9e63282ff 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/LedgerHandle.java
@@ -978,6 +978,65 @@ public class LedgerHandle implements WriteHandle {
return future;
}
+ @Override
+ public CompletableFuture<LedgerEntries> batchReadUnconfirmedAsync(long
startEntry, int maxCount, long maxSize) {
+ // Little sanity check
+ if (startEntry < 0 || maxCount < 0 || maxSize < 0) {
+ LOG.error("IncorrectParameterException on ledgerId:{}
firstEntry:{} when batch read", ledgerId, startEntry);
+ return FutureUtils.exception(new BKIncorrectParameterException());
+ }
+
+ long lastEntry = startEntry + maxCount - 1;
+ if (notSupportBatchRead()) {
+ return readUnconfirmedAsync(startEntry, lastEntry);
+ }
+ if (clientCtx.isClientClosed()) {
+ return
FutureUtils.exception(BKException.create(ClientClosedException));
+ }
+
+ CompletableFuture<LedgerEntries> future = new CompletableFuture<>();
+ batchReadEntriesInternalAsync(startEntry, maxCount, maxSize, false)
+ .whenCompleteAsync((entries, error) ->
completeBatchReadUnconfirmed(
+ startEntry, lastEntry, entries, error, future),
+ clientCtx.getMainWorkerPool().chooseThread(ledgerId));
+ return future;
+ }
+
+ private void completeBatchReadUnconfirmed(long startEntry,
+ long lastEntry,
+ LedgerEntries entries,
+ Throwable error,
+ CompletableFuture<LedgerEntries>
future) {
+ if (error == null) {
+ future.complete(entries);
+ return;
+ }
+
+ if (error instanceof BKException.BKBookieHandleNotAvailableException) {
+ notSupportBatch = true;
+ readUnconfirmedAsync(startEntry, lastEntry)
+ .whenComplete((fallbackEntries, fallbackError) ->
+
completeBatchReadUnconfirmedResult(fallbackEntries, fallbackError, future));
+ return;
+ }
+
+ completeBatchReadUnconfirmedResult(null, error, future);
+ }
+
+ private void completeBatchReadUnconfirmedResult(LedgerEntries entries,
+ Throwable error,
+
CompletableFuture<LedgerEntries> future) {
+ if (error == null) {
+ future.complete(entries);
+ return;
+ }
+ if (error instanceof BKException) {
+ future.completeExceptionally(error);
+ return;
+ }
+
future.completeExceptionally(BKException.create(Code.UnexpectedConditionException));
+ }
+
private boolean notSupportBatchRead() {
if (!clientCtx.getConf().batchReadEnabled) {
return true;
diff --git
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/ReadHandle.java
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/ReadHandle.java
index 8e2e633a35..18ac4c73fc 100644
---
a/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/ReadHandle.java
+++
b/bookkeeper-server/src/main/java/org/apache/bookkeeper/client/api/ReadHandle.java
@@ -91,6 +91,22 @@ public interface ReadHandle extends Handle {
return FutureUtils.result(batchReadAsync(startEntry, maxCount,
maxSize), BKException.HANDLER);
}
+
+ /**
+ * Read a sequence of entries in batch asynchronously, allowing to read
after the LastAddConfirmed range.
+ *
+ * @param startEntry
+ * start entry id
+ * @param maxCount
+ * the total entries count.
+ * @param maxSize
+ * the total entries size.
+ * @return he results of the operation
+ */
+ default CompletableFuture<LedgerEntries> batchReadUnconfirmedAsync(long
startEntry, int maxCount, long maxSize) {
+ return readUnconfirmedAsync(startEntry, startEntry + maxCount - 1);
+ }
+
/**
* Read a sequence of entries asynchronously, allowing to read after the
LastAddConfirmed range.
* <br>This is the same of
diff --git
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java
index b1def134d4..3090cd5134 100644
---
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java
+++
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/MockBookKeeperTestCase.java
@@ -41,6 +41,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -247,6 +248,7 @@ public abstract class MockBookKeeperTestCase {
setupBookieWatcherForNewEnsemble();
setupBookieWatcherForEnsembleChange();
setupBookieClientReadEntry();
+ setupBookieClientBatchReadEntry();
setupBookieClientReadLac();
setupBookieClientAddEntry();
setupBookieClientForceLedger();
@@ -546,6 +548,84 @@ public abstract class MockBookKeeperTestCase {
any(), anyInt(), any(), anyBoolean());
}
+ protected void setupBookieClientBatchReadEntry() {
+ final Stubber stub = doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ BookieId bookieSocketAddress = (BookieId) args[0];
+ long ledgerId = (Long) args[1];
+ long startEntryId = (Long) args[2];
+ int maxCount = (Integer) args[3];
+ long maxSize = (Long) args[4];
+ BookkeeperInternalCallbacks.BatchedReadEntryCallback callback =
+ (BookkeeperInternalCallbacks.BatchedReadEntryCallback) args[5];
+ Object ctx = args[6];
+ boolean fenced = (((Integer) args[7]) &
BookieProtocol.FLAG_DO_FENCING) == BookieProtocol.FLAG_DO_FENCING;
+
+ executor.executeOrdered(ledgerId, () -> {
+ DigestManager macManager = null;
+ try {
+ macManager = getDigestType(ledgerId);
+ } catch (GeneralSecurityException gse){
+ LOG.error("Initialize macManager fail", gse);
+ }
+
+ Map<Long, MockEntry> mockEntries = new LinkedHashMap<>();
+ for (long entryId = startEntryId; maxCount <= 0 || entryId <
startEntryId + maxCount; entryId++) {
+ MockEntry mockEntry;
+ try {
+ mockEntry = getMockLedgerEntry(ledgerId,
bookieSocketAddress, entryId);
+ if (mockEntry == null) {
+ break;
+ }
+ mockEntries.put(entryId, mockEntry);
+ } catch (BKException bke) {
+ LOG.info("batchReadEntryAndFenceLedger - occur
BKException {}@{} at {}", entryId, ledgerId,
+ bookieSocketAddress);
+ callback.readEntriesComplete(bke.getCode(), ledgerId,
startEntryId, null, ctx);
+ return;
+ }
+
+ }
+
+ if (fenced) {
+ fencedLedgers.add(ledgerId);
+ }
+
+ ByteBufList bufList = ByteBufList.get();
+ long totalBytes = 0;
+ if (mockEntries.isEmpty()) {
+
callback.readEntriesComplete(BKException.Code.NoSuchEntryException, ledgerId,
startEntryId, null,
+ ctx);
+ } else {
+ for (Map.Entry<Long, MockEntry> kv :
mockEntries.entrySet()) {
+ long entryId = kv.getKey();
+ MockEntry mockEntry = kv.getValue();
+ ReferenceCounted entry =
macManager.computeDigestAndPackageForSending(entryId,
+ mockEntry.lastAddConfirmed,
mockEntry.payload.length,
+ Unpooled.wrappedBuffer(mockEntry.payload), new
byte[20], 0);
+ ByteBuf entryData = MockBookieClient.copyData(entry);
+ entry.release();
+ totalBytes += entryData.readableBytes();
+ if (totalBytes > maxSize) {
+ entryData.release();
+ break;
+ }
+ bufList.add(entryData);
+ }
+ callback.readEntriesComplete(BKException.Code.OK,
ledgerId, startEntryId, bufList, ctx);
+ }
+ });
+ return null;
+ });
+
+ stub.when(bookieClient).batchReadEntries(any(BookieId.class),
anyLong(), anyLong(), anyInt(), anyLong(),
+ any(), any(), anyInt());
+ stub.when(bookieClient).batchReadEntries(any(BookieId.class),
anyLong(), anyLong(), anyInt(), anyLong(),
+ any(), any(), anyInt(), any());
+ stub.when(bookieClient).batchReadEntries(any(BookieId.class),
anyLong(), anyLong(), anyInt(), anyLong(),
+ any(), any(), anyInt(), any(), anyBoolean());
+ }
+
@SuppressWarnings("unchecked")
protected void setupBookieClientReadLac() {
final Stubber stub = doAnswer(invocation -> {
diff --git
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
index 75ba8e30b9..b8eebb5180 100644
---
a/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
+++
b/bookkeeper-server/src/test/java/org/apache/bookkeeper/client/api/BookKeeperApiTest.java
@@ -32,6 +32,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doAnswer;
import io.netty.buffer.Unpooled;
import java.nio.ByteBuffer;
@@ -39,13 +44,18 @@ import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.bookkeeper.client.BKException;
+import org.apache.bookkeeper.client.BKException.BKClientClosedException;
import org.apache.bookkeeper.client.BKException.BKDigestMatchException;
import org.apache.bookkeeper.client.BKException.BKDuplicateEntryIdException;
+import org.apache.bookkeeper.client.BKException.BKIncorrectParameterException;
import org.apache.bookkeeper.client.BKException.BKLedgerFencedException;
+import org.apache.bookkeeper.client.BKException.BKNoSuchEntryException;
import
org.apache.bookkeeper.client.BKException.BKNoSuchLedgerExistsOnMetadataServerException;
import org.apache.bookkeeper.client.BKException.BKUnauthorizedAccessException;
import org.apache.bookkeeper.client.MockBookKeeperTestCase;
import org.apache.bookkeeper.conf.ClientConfiguration;
+import org.apache.bookkeeper.net.BookieId;
+import org.apache.bookkeeper.proto.BookkeeperInternalCallbacks;
import org.apache.bookkeeper.util.LoggerOutput;
import org.junit.Rule;
import org.junit.jupiter.api.Test;
@@ -72,6 +82,59 @@ public class BookKeeperApiTest extends
MockBookKeeperTestCase {
}
}
+ private long createLedgerWithEntries(int numEntries) throws Exception {
+ try (WriteHandle writer = result(newCreateLedgerOp()
+ .withAckQuorumSize(2)
+ .withWriteQuorumSize(2)
+ .withEnsembleSize(2)
+ .withPassword(password)
+ .execute())) {
+ long ledgerId = writer.getId();
+ for (int i = 0; i < numEntries; i++) {
+ writer.append(ByteBuffer.wrap(data));
+ }
+ return ledgerId;
+ }
+ }
+
+ private ReadHandle openReadHandle(long ledgerId) throws Exception {
+ return result(newOpenLedgerOp()
+ .withPassword(password)
+ .withRecovery(false)
+ .withLedgerId(ledgerId)
+ .execute());
+ }
+
+ private void failBatchReadRequests(int rc) {
+ doAnswer(invocation -> {
+ BookkeeperInternalCallbacks.BatchedReadEntryCallback callback =
invocation.getArgument(5);
+ long ledgerId = invocation.getArgument(1);
+ long startEntryId = invocation.getArgument(2);
+ Object ctx = invocation.getArgument(6);
+ callback.readEntriesComplete(rc, ledgerId, startEntryId, null,
ctx);
+ return null;
+ }).when(bookieClient).batchReadEntries(any(BookieId.class), anyLong(),
anyLong(), anyInt(), anyLong(),
+ any(), any(), anyInt());
+ doAnswer(invocation -> {
+ BookkeeperInternalCallbacks.BatchedReadEntryCallback callback =
invocation.getArgument(5);
+ long ledgerId = invocation.getArgument(1);
+ long startEntryId = invocation.getArgument(2);
+ Object ctx = invocation.getArgument(6);
+ callback.readEntriesComplete(rc, ledgerId, startEntryId, null,
ctx);
+ return null;
+ }).when(bookieClient).batchReadEntries(any(BookieId.class), anyLong(),
anyLong(), anyInt(), anyLong(),
+ any(), any(), anyInt(), any());
+ doAnswer(invocation -> {
+ BookkeeperInternalCallbacks.BatchedReadEntryCallback callback =
invocation.getArgument(5);
+ long ledgerId = invocation.getArgument(1);
+ long startEntryId = invocation.getArgument(2);
+ Object ctx = invocation.getArgument(6);
+ callback.readEntriesComplete(rc, ledgerId, startEntryId, null,
ctx);
+ return null;
+ }).when(bookieClient).batchReadEntries(any(BookieId.class), anyLong(),
anyLong(), anyInt(), anyLong(),
+ any(), any(), anyInt(), any(), anyBoolean());
+ }
+
@Test
public void testWriteHandle() throws Exception {
try (WriteHandle writer = result(newCreateLedgerOp()
@@ -429,6 +492,91 @@ public class BookKeeperApiTest extends
MockBookKeeperTestCase {
}
}
+ @Test
+ public void testBatchedReadUnconfirmedAsync() throws Exception {
+ long lId = createLedgerWithEntries(3);
+ try (ReadHandle reader = openReadHandle(lId)) {
+ long lac = reader.getLastAddConfirmed();
+ assertEquals(2, lac);
+
+ try (LedgerEntries entries =
result(reader.batchReadUnconfirmedAsync(0, 5, 5 * 1024 * 1024))) {
+ AtomicLong i = new AtomicLong(0);
+ for (LedgerEntry e : entries) {
+ assertEquals(i.getAndIncrement(), e.getEntryId());
+ assertArrayEquals(data, e.getEntryBytes());
+ }
+ i.set(0);
+ entries.forEach((e) -> {
+ assertEquals(i.getAndIncrement(), e.getEntryId());
+ assertArrayEquals(data, e.getEntryBytes());
+ });
+ }
+ }
+ }
+
+ @Test
+ public void testBatchedReadUnconfirmedAsyncRejectsInvalidArguments()
throws Exception {
+ long lId = createLedgerWithEntries(3);
+ try (ReadHandle reader = openReadHandle(lId)) {
+ assertThrows(BKIncorrectParameterException.class,
+ () -> result(reader.batchReadUnconfirmedAsync(-1, 1,
1024)));
+ assertThrows(BKIncorrectParameterException.class,
+ () -> result(reader.batchReadUnconfirmedAsync(0, -1,
1024)));
+ assertThrows(BKIncorrectParameterException.class,
+ () -> result(reader.batchReadUnconfirmedAsync(0, 1, -1)));
+ }
+ }
+
+ @Test
+ public void
testBatchedReadUnconfirmedAsyncPropagatesNoSuchEntryException() throws
Exception {
+ long lId = createLedgerWithEntries(3);
+ try (ReadHandle reader = openReadHandle(lId)) {
+ assertThrows(BKNoSuchEntryException.class,
+ () -> result(reader.batchReadUnconfirmedAsync(5, 1,
1024)));
+ }
+ }
+
+ @Test
+ public void testBatchedReadUnconfirmedAsyncFailsIfClientClosed() throws
Exception {
+ long lId = createLedgerWithEntries(3);
+ try (ReadHandle reader = openReadHandle(lId)) {
+ closeBookkeeper();
+ assertThrows(BKClientClosedException.class,
+ () -> result(reader.batchReadUnconfirmedAsync(0, 1,
1024)));
+ }
+ }
+
+ @Test
+ public void
testBatchedReadUnconfirmedAsyncFallsBackWhenBatchReadIsUnsupported() throws
Exception {
+ long lId = createLedgerWithEntries(3);
+
failBatchReadRequests(BKException.Code.BookieHandleNotAvailableException);
+
+ try (ReadHandle reader = openReadHandle(lId);
+ LedgerEntries entries =
result(reader.batchReadUnconfirmedAsync(0, 3, 5 * 1024 * 1024))) {
+ AtomicLong i = new AtomicLong(0);
+ for (LedgerEntry e : entries) {
+ assertEquals(i.getAndIncrement(), e.getEntryId());
+ assertArrayEquals(data, e.getEntryBytes());
+ }
+ assertEquals(3, i.get());
+ }
+ }
+
+ @Test
+ public void testBatchedReadUnconfirmedAsyncTreatsZeroMaxCountAsUnlimited()
throws Exception {
+ long lId = createLedgerWithEntries(3);
+
+ try (ReadHandle reader = openReadHandle(lId);
+ LedgerEntries entries =
result(reader.batchReadUnconfirmedAsync(0, 0, 5 * 1024 * 1024))) {
+ AtomicLong i = new AtomicLong(0);
+ for (LedgerEntry e : entries) {
+ assertEquals(i.getAndIncrement(), e.getEntryId());
+ assertArrayEquals(data, e.getEntryBytes());
+ }
+ assertEquals(3, i.get());
+ }
+ }
+
@Test
public void testBKExceptionCodeLogger() {
assertEquals("OK: No problem", BKException.codeLogger(0).toString());