{"record":{"id":"79d822b78ea6d9cf","repo":"apache/pulsar","slug":"a-future-has-already-been-attached-to-this-instanc","errorCode":null,"errorMessage":"A future has already been attached to this instance.","messagePattern":"A future has already been attached to this instance\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"pulsar-common/src/main/java/org/apache/pulsar/common/util/CompletableFutureCancellationHandler.java","lineNumber":76,"sourceCode":"     *\n     * @param <T> the result type of the future\n     * @return a new future instance\n     */\n    public <T> CompletableFuture<T> createFuture() {\n        CompletableFuture<T> future = new CompletableFuture<>();\n        attachToFuture(future);\n        return future;\n    }\n\n    /**\n     * Attaches the cancellation handler to handle cancels\n     * and timeouts. A cancellation handler instance can be used only once.\n     *\n     * @param future the future to attach the handler to\n     */\n    public synchronized void attachToFuture(CompletableFuture<?> future) {\n        if (attached) {\n            throw new IllegalStateException(\"A future has already been attached to this instance.\");\n        }\n        attached = true;\n        future.whenComplete(whenCompleteFunction());\n    }\n\n    /**\n     * Set the action to run when the future gets cancelled or timeouts.\n     * The cancellation or timeout might be originating from any \"upstream\" future.\n     * The implementation ensures that the cancel action gets called once.\n     * Handles possible race conditions that might happen when the future gets cancelled\n     * before the cancel action is set to this handler. In this case, the\n     * cancel action gets called when the action is set.\n     *\n     * @param cancelAction the action to run when the the future gets cancelled or timeouts\n     */\n    public void setCancelAction(Runnable cancelAction) {\n        if (this.cancelAction != null || cancelHandled.get()) {\n            throw new IllegalStateException(\"cancelAction can only be set once.\");","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-common/src/main/java/org/apache/pulsar/common/util/CompletableFutureCancellationHandler.java#L58-L94","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\nprivate final CompletableFutureCancellationHandler handler = new CompletableFutureCancellationHandler();\n// reused across reads -> second attach throws\n// after\nCompletableFuture<T> fut = new CompletableFuture<>();\nCompletableFutureCancellationHandler handler = new CompletableFutureCancellationHandler(); // fresh per operation\nhandler.attachToFuture(fut);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    handler.attachToFuture(future);\n} catch (IllegalStateException e) {\n    throw new IllegalStateException(\"handler reused across futures — allocate a new CompletableFutureCancellationHandler per operation\", e);\n}","preventionTips":["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"],"tags":["concurrency","async","future","state"],"backgroundTag":"illegal-state-reuse","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}