apache/pulsar · warning · IOException

IOException

Error message

IOException

What it means

PulsarLedgerManager.close() shuts down its internal scheduled executor and waits up to 10 seconds for termination. If the waiting thread is interrupted, the InterruptedException is converted to an IOException (after restoring the interrupt flag), signaling that close could not confirm a clean shutdown of background tasks.

Source

Thrown at pulsar-metadata/src/main/java/org/apache/pulsar/metadata/bookkeeper/PulsarLedgerManager.java:367

            }
        }, context, successRc, failureRc);
    }

    @Override
    public LedgerRangeIterator getLedgerRanges(long ledgerId) {
        LedgerRangeIterator iteratorA = new LegacyHierarchicalLedgerRangeIterator(store, ledgerRootPath);
        LedgerRangeIterator iteratorB = new LongHierarchicalLedgerRangeIterator(store, ledgerRootPath);
        return new CombinedLedgerRangeIterator(iteratorA, iteratorB);
    }

    @Override
    public void close() throws IOException {
        scheduler.shutdownNow();
        try {
            scheduler.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw new IOException(ie);
        }
    }

    public String getLedgerPath(long ledgerId) {
        return this.ledgerRootPath + StringUtils.getHybridHierarchicalLedgerPath(ledgerId);
    }

    private long getLedgerId(String ledgerPath) throws IOException {
        if (!ledgerPath.startsWith(ledgerRootPath)) {
            throw new IOException("it is not a valid hashed path name : " + ledgerPath);
        }
        String hierarchicalPath = ledgerPath.substring(ledgerRootPath.length() + 1);
        return StringUtils.stringToLongHierarchicalLedgerId(hierarchicalPath);
    }


    /**
     * ReadLedgerMetadataTask class.

View on GitHub (pinned to 820761864e)

Solutions

  1. Avoid interrupting the thread performing close(); let the 10s awaitTermination complete during orderly shutdown.
  2. Restore the interrupt status in the caller and proceed with resource cleanup; the executor was already shutdownNow()'ed so background tasks are stopping regardless.
  3. If close() hangs near 10s regularly, audit long-period scheduled tasks in the ledger manager and shorten their cycles before shutdown.
  4. Ensure only one component owns the lifecycle so close() is not invoked concurrently from competing shutdown paths.

Example fix

// before
ledgerManager.close();
// after
try {
    ledgerManager.close();
} catch (IOException e) {
    Thread.currentThread().interrupt();
    LOG.warn("Interrupted while closing ledger manager", e);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Confirm the thread is not already interrupted before closing
if (Thread.currentThread().isInterrupted()) {
    // clear and re-close, or defer close to a non-interrupted thread
    Thread.interrupted();
}

Try / catch

try {
    ledgerManager.close();
} catch (IOException e) {
    Thread.currentThread().interrupt(); // flag was set by close(); keep it
    LOG.warn("Ledger manager close interrupted; executor already shut down", e);
}

Prevention

When it happens

Trigger: Calling close() on the PulsarLedgerManager while the current thread is interrupted — commonly during BookKeeper client shutdown, application stop, or executor shutdownNow() cascading an interrupt.

Common situations: Graceful broker shutdown racing ledger-manager cleanup; test teardown interrupting the closing thread; timeouts in outer shutdown logic issuing interrupt while close() is awaiting executor termination.

Related errors


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