apache/pulsar · warning · ReplicationException.UnavailableException

Interrupted while connecting metadata store

Error message

Interrupted while connecting metadata store

What it means

Thrown by getLedgerUnreplicationInfo when the thread blocked waiting on store.get(path).get(...) is interrupted. The code re-asserts the interrupt flag (Thread.currentThread().interrupt()) before wrapping the InterruptedException in ReplicationException.UnavailableException so callers and the executor keep correct interruption semantics. It means the read of the under-replicated ledger state was cancelled rather than failed.

Source

Thrown at pulsar-metadata/src/main/java/org/apache/pulsar/metadata/bookkeeper/PulsarLedgerUnderreplicationManager.java:319

            }

            byte[] data = optRes.get().getValue();

            UnderreplicatedLedgerFormat underreplicatedLedgerFormat = new UnderreplicatedLedgerFormat();

            underreplicatedLedgerFormat.parseFromTextFormat(data);
            PulsarUnderreplicatedLedger underreplicatedLedger = new PulsarUnderreplicatedLedger(ledgerId);
            List<String> replicaList = underreplicatedLedgerFormat.getReplicasList();
            long ctime = (underreplicatedLedgerFormat.hasCtime() ? underreplicatedLedgerFormat.getCtime()
                    : UnderreplicatedLedger.UNASSIGNED_CTIME);
            underreplicatedLedger.setCtime(ctime);
            underreplicatedLedger.setReplicaList(replicaList);
            return underreplicatedLedger;
        } catch (ExecutionException | TimeoutException ee) {
            throw new ReplicationException.UnavailableException("Error contacting with metadata store", ee);
        } catch (InterruptedException ie) {
            Thread.currentThread().interrupt();
            throw new ReplicationException.UnavailableException("Interrupted while connecting metadata store", ie);
        } catch (RuntimeException pe) {
            throw new ReplicationException.UnavailableException("Error parsing proto message", pe);
        }
    }

    @Override
    public CompletableFuture<Void> markLedgerUnderreplicatedAsync(long ledgerId, Collection<String> missingReplicas) {
        log.debug().attr("ledgerId", ledgerId).attr("missingReplicas", missingReplicas)
                .log("markLedgerUnderreplicated");
        final String path = getUrLedgerPath(ledgerId);
        final CompletableFuture<Void> createFuture = new CompletableFuture<>();
        tryMarkLedgerUnderreplicatedAsync(path, missingReplicas, createFuture);
        return createFuture;
    }

    private void tryMarkLedgerUnderreplicatedAsync(final String path,
                                                   final Collection<String> missingReplicas,
                                                   final CompletableFuture<Void> finalFuture) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Treat it as expected cancellation: let the thread exit and do not swallow or clear the interrupt flag.
  2. If it happens unexpectedly, audit which component interrupts the Auditor thread (shutdown hooks, schedulers) and ensure reads are not started on threads about to be cancelled.
  3. If shutdown interrupts are too aggressive, configure orderly shutdown so in-flight replication checks complete before executor termination.
  4. Retry the ledger check after restart; the under-replicated state is persistent in the metadata store.

Example fix

// before
try {
    UnderreplicatedLedger l = urManager.getLedgerUnreplicationInfo(ledgerId);
} catch (ReplicationException.UnavailableException e) {
    log.error("failed", e); // ignores interrupt state
}
// after
try {
    UnderreplicatedLedger l = urManager.getLedgerUnreplicationInfo(ledgerId);
} catch (ReplicationException.UnavailableException e) {
    if (Thread.currentThread().isInterrupted()) {
        return; // shutdown in progress, stop the loop
    }
    retryWithBackoff(...);
}
Defensive patterns

Strategy: try-catch

Validate before calling

// skip the call if the thread is already interrupted
if (Thread.currentThread().isInterrupted()) {
    return; // don't start blocking store reads on a cancelled thread
}

Type guard

static boolean wasInterrupted(ReplicationException.UnavailableException e) {
    return e.getCause() instanceof InterruptedException;
}

Try / catch

try {
    return urManager.getLedgerUnreplicationInfo(ledgerId);
} catch (ReplicationException.UnavailableException e) {
    if (Thread.currentThread().isInterrupted()) {
        return null; // shutdown path: exit promptly, don't retry
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling getLedgerUnreplicationInfo from a thread that gets interrupted while blocked on the metadata store future — e.g. broker/Auditor shutdown, executor.shutdownNow(), or another component cancelling the worker thread mid blocking get.

Common situations: Graceful shutdown of the Pulsar broker's Auditor thread; a scheduled task cancelled on timeout by an outer scheduler; test harnesses interrupting leaked blocking threads; thread pool termination during redeploy.

Related errors


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