apache/pulsar · warning · IOException

Interrupted while reading ledgers at path ${path}

Error message

Interrupted while reading ledgers at path ${path}

What it means

getChildrenAt() blocks on a CompletableFuture with .get(timeout); if the waiting thread is interrupted, it re-asserts the interrupt flag and wraps the InterruptedException in an IOException with the message 'Interrupted while reading ledgers at path <path>'. This is not a store failure — the caller's thread was interrupted while blocked.

Source

Thrown at pulsar-metadata/src/main/java/org/apache/pulsar/metadata/bookkeeper/LongHierarchicalLedgerRangeIterator.java:69

     * Returns all children with path as a parent.  If path is non-existent,
     * returns an empty list anyway (after all, there are no children there).
     * Maps all exceptions (other than NoNode) to IOException in keeping with
     * LedgerRangeIterator.
     *
     * @param path
     * @return Iterator into set of all children with path as a parent
     * @throws IOException
     */
    List<String> getChildrenAt(String path) throws IOException {
        try {
            return store.sync(path).thenCompose(__ -> store.getChildrenFromStore(path))
                    .get(AbstractMetadataDriver.BLOCKING_CALL_TIMEOUT, TimeUnit.MILLISECONDS);
        } catch (ExecutionException | TimeoutException e) {
            log.debug().attr("path", path).log("Failed to get children");
            throw new IOException(e);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw new IOException("Interrupted while reading ledgers at path " + path, ie);
        }
    }

    /**
     * Represents the ledger range rooted at a leaf node, returns at most one LedgerRange.
     */
    class LeafIterator implements LedgerManager.LedgerRangeIterator {
        // Null iff iteration is complete
        LedgerManager.LedgerRange range;

        LeafIterator(String path) throws IOException {
            List<String> ledgerLeafNodes = getChildrenAt(path);
            Set<Long> ledgerIds = HierarchicalLedgerUtils.ledgerListToSet(ledgerLeafNodes, ledgerRootPath, path);
            log.debug().attr("hashNode", path)
                    .attr("ledgers", ledgerIds)
                    .log("All active ledgers from ZK for hash node");
            if (!ledgerIds.isEmpty()) {
                range = new LedgerManager.LedgerRange(ledgerIds);

View on GitHub (pinned to 820761864e)

Solutions

  1. Let shutdown proceed — this is expected during termination; stop the iteration.
  2. Avoid interrupting worker threads running ledger scans, or run scans in a cancellable task you control.
  3. If seen unexpectedly, audit for code calling interrupt() on threads doing metadata reads.
  4. Preserve and honor the interrupt flag (the library already does Thread.currentThread().interrupt()); do not swallow it in retry loops.

Example fix

// before
catch (IOException e) { e.printStackTrace(); /* keep iterating */ }
// after
catch (IOException e) {
    if (Thread.currentThread().isInterrupted()) {
        return; // shutdown in progress, stop scanning
    }
    throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check interrupt status before starting the scan
if (Thread.currentThread().isInterrupted()) { return; }

Try / catch

try { ... } catch (IOException e) {
  if (Thread.currentThread().isInterrupted()) { /* stop cleanly */ return; }
  throw e;
}

Prevention

When it happens

Trigger: Thread interruption while getChildrenAt(path) is blocked on .get(..., BLOCKING_CALL_TIMEOUT, ...) — typically broker shutdown, task cancellation, or an executor that interrupts workers.

Common situations: Graceful shutdown of a broker/bookie mid ledger-range scan; a cancelled scheduled job iterating ledgers; thread pool shutdownNow during long hierarchical ledger enumeration.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/d4f99d192a1237ad. Report an issue: GitHub.