apache/pulsar · error · IllegalStateException
A future has already been attached to this instance.
Error message
A future has already been attached to this instance.
What it means
Thrown by CompletableFutureCancellationHandler.attachToFuture when a future is attached to a handler instance that already has one. Each cancellation handler is single-use by design: it tracks exactly one future's cancellation/timeout, so reusing it across futures would corrupt its cancellation semantics.
Source
Thrown at pulsar-common/src/main/java/org/apache/pulsar/common/util/CompletableFutureCancellationHandler.java:76
*
* @param <T> the result type of the future
* @return a new future instance
*/
public <T> CompletableFuture<T> createFuture() {
CompletableFuture<T> future = new CompletableFuture<>();
attachToFuture(future);
return future;
}
/**
* Attaches the cancellation handler to handle cancels
* and timeouts. A cancellation handler instance can be used only once.
*
* @param future the future to attach the handler to
*/
public synchronized void attachToFuture(CompletableFuture<?> future) {
if (attached) {
throw new IllegalStateException("A future has already been attached to this instance.");
}
attached = true;
future.whenComplete(whenCompleteFunction());
}
/**
* Set the action to run when the future gets cancelled or timeouts.
* The cancellation or timeout might be originating from any "upstream" future.
* The implementation ensures that the cancel action gets called once.
* Handles possible race conditions that might happen when the future gets cancelled
* before the cancel action is set to this handler. In this case, the
* cancel action gets called when the action is set.
*
* @param cancelAction the action to run when the the future gets cancelled or timeouts
*/
public void setCancelAction(Runnable cancelAction) {
if (this.cancelAction != null || cancelHandled.get()) {
throw new IllegalStateException("cancelAction can only be set once.");View on GitHub (pinned to 820761864e)
Solutions
- Create a new CompletableFutureCancellationHandler per future/operation instead of reusing the instance
- Reset the owning object so each request constructs a fresh handler (typical consumer pattern: new handler in internalReceiveAsync)
- Audit code paths named in callers (readNextAsync, createFuture, whenCancelledOrTimedOut) for double-attach on retry paths
- Guard application code so the handler is a local variable, not a shared field
Example fix
// before private final CompletableFutureCancellationHandler handler = new CompletableFutureCancellationHandler(); // reused across reads -> second attach throws // after CompletableFuture<T> fut = new CompletableFuture<>(); CompletableFutureCancellationHandler handler = new CompletableFutureCancellationHandler(); // fresh per operation handler.attachToFuture(fut);
Defensive patterns
Strategy: try-catch
Try / catch
try {
handler.attachToFuture(future);
} catch (IllegalStateException e) {
throw new IllegalStateException("handler reused across futures — allocate a new CompletableFutureCancellationHandler per operation", e);
} Prevention
- Create a new handler per future/request; never store it in a shared field
- Treat the handler as single-use per its documented contract
- Ensure retry loops construct fresh handler + future pairs
- Review consumer code paths (readNextAsync, createFuture) for double attach
When it happens
Trigger: Calling attachToFuture twice on the same handler instance — e.g. a consumer receive loop reusing a cached handler for readNextAsync across multiple calls, or createFuture/whenCancelledOrTimedOut invoked again after an initial attach.
Common situations: Caching a handler in a field and re-attaching on every retry/request; sharing one handler across concurrent reads; a refactor moving attachToFuture into code that can run twice for the same object instance (e.g. re-subscription logic).
Related errors
- cancelAction can only be set once.
- ManagedLedgerFactory is already closed.
- ManagedLedger ${name} has already been closed
- Migration is already in progress (phase: ${phase})
- Concurrent modification
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/79d822b78ea6d9cf.
Report an issue: GitHub.