apache/pulsar · error · IOException
java.io.IOException (wraps InterruptedException | ExecutionE
Error message
java.io.IOException (wraps InterruptedException | ExecutionException when getting LedgerEntries)
What it means
BlockAwareSegmentInputStreamImpl.readNextEntriesFromLedger fetches ledger entries from BookKeeper asynchronously and blocks on the future. If the future completes with InterruptedException or ExecutionException while getting LedgerEntries, the exception is logged and wrapped in a java.io.IOException so the InputStream contract is preserved.
Source
Thrown at tiered-storage/jcloud/src/main/java/org/apache/bookkeeper/mledger/offload/jcloud/impl/BlockAwareSegmentInputStreamImpl.java:214
ByteBuf buf = entry.getEntryBuffer().retain();
int entryLength = buf.readableBytes();
long entryId = entry.getEntryId();
CompositeByteBuf entryBuf = PulsarByteBufAllocator.DEFAULT.compositeBuffer(2);
ByteBuf entryHeaderBuf = PulsarByteBufAllocator.DEFAULT.buffer(ENTRY_HEADER_SIZE, ENTRY_HEADER_SIZE);
entryHeaderBuf.writeInt(entryLength).writeLong(entryId);
entryBuf.addComponents(true, entryHeaderBuf, buf);
entries.add(entryBuf);
}
return entries;
} catch (InterruptedException | ExecutionException e) {
log.error().exception(e).log("Exception when getting LedgerEntries");
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
throw new IOException(e);
}
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException("The given bytes are null");
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException("off=" + off + ", len=" + len + ", b.length=" + b.length);
} else if (len == 0) {
return 0;
}
int offset = off;
int readLen = len;
int readBytes = 0;
// reading header
if (dataBlockHeaderStream.available() > 0) {View on GitHub (pinned to 820761864e)
Solutions
- Inspect the wrapped cause: if the source ledger was deleted after offload, read from the offload copy instead of the original ledger
- Check bookie availability and the BK client's read timeout/operation settings
- Avoid closing/interrupting the BookKeeper client thread performing offload reads; restore the interrupt flag if InterruptedException
- Retry the read once the BookKeeper cluster is healthy
Example fix
// before
readStream.read(buf); // IOException wrapping ExecutionException
// after
try { readStream.read(buf); }
catch (IOException e) {
if (e.getCause() instanceof ExecutionException
&& rootCause(e) instanceof LedgerDeletedException) {
readFromOffloadCopy();
} else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the source ledger still exists before reading entries
try {
bk.openLedger(ledgerId, digestType, password).close();
} catch (BKException.BKNoSuchLedgerExistsException e) {
useOffloadCopy(ledgerId); // ledger already deleted after offload
} Try / catch
try {
int n = stream.read(buf);
} catch (IOException e) {
Throwable root = rootCause(e);
if (root instanceof InterruptedException) {
Thread.currentThread().interrupt();
} else if (root instanceof BKException) {
log.error("BK read failed for offloaded segment: {}", String.valueOf(root));
}
} Prevention
- Check the offload+delete policy before reading original ledgers
- Keep the BookKeeper client alive for the whole offload/read operation
- Monitor bookie availability and read-timeout configs
- Preserve interrupt flags; never swallow InterruptedException
When it happens
Trigger: readEntries -> readNextEntriesFromLedger calls ledger.asyncReadEntries(...).get(); BK client errors (LedgerDeletedException, NoSuchEntryException, client closed), timeouts, or thread interruption surface as ExecutionException/InterruptedException and become this IOException.
Common situations: Offloaded-segment read while the source ledger was deleted (after offload+delete policy) or ledgers were removed from bookies; BookKeeper client shutting down during offload/read; bookie unavailability causing read timeout.
Related errors
- Fail to read LedgerMetadata for ledgerId ${key}
- Error seeking, new position %d < current position %d
- Data block header magic word not match. read: ${magic} expec
- Cursor %s mark-delete position %s is ahead of the last posit
- IOException
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/327c24def46a065c.
Report an issue: GitHub.