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
- Avoid interrupting the thread performing close(); let the 10s awaitTermination complete during orderly shutdown.
- Restore the interrupt status in the caller and proceed with resource cleanup; the executor was already shutdownNow()'ed so background tasks are stopping regardless.
- If close() hangs near 10s regularly, audit long-period scheduled tasks in the ledger manager and shorten their cycles before shutdown.
- 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
- Sequence shutdown so ledger manager close happens before interrupt-issuing phases
- Allow the full awaitTermination window; do not preempt with interrupts
- Give each resource a single lifecycle owner to avoid concurrent close() calls
- Log and assert interrupt status in shutdown hooks to catch premature interrupts early
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
- Interrupted while reading ledgers at path ${path}
- Interrupted while contacting metadata store
- Interrupted initializing OAuth2 IdP TLS factory
- Interrupted at fetching schema info for <SchemaUtils.getStri
- RuntimeException
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/bf7070f181fa3b51.
Report an issue: GitHub.