{"record":{"id":"a228e209c72c4cc0","repo":"apache/pulsar","slug":"close-interrupted-a228e2","errorCode":null,"errorMessage":"Close interrupted","messagePattern":"Close interrupted","errorType":"exception","errorClass":"org.apache.pulsar.client.impl.v5.PulsarClientException","httpStatus":null,"severity":"warning","filePath":"pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableQueueConsumer.java","lineNumber":266,"sourceCode":"        }\n        var future = segmentConsumers.get(id.segmentId());\n        if (future != null) {\n            future.thenAccept(c -> c.negativeAcknowledge(id.v4MessageId()));\n        }\n    }\n\n    @Override\n    public AsyncQueueConsumer<T> async() {\n        return asyncView;\n    }\n\n    @Override\n    public void close() throws PulsarClientException {\n        try {\n            closeAsync().get();\n        } catch (InterruptedException e) {\n            Thread.currentThread().interrupt();\n            throw new PulsarClientException(\"Close interrupted\", e);\n        } catch (ExecutionException e) {\n            throw new PulsarClientException(e.getCause());\n        }\n    }\n\n    // --- Async internals ---\n\n    @Override\n    public CompletableFuture<Message<T>> receiveAsync() {\n        return receiveQueue.receiveAsync();\n    }\n\n    @Override\n    public CompletableFuture<Void> closeAsync() {\n        closed = true;\n        receiveQueue.close();\n        dagWatch.close();\n","sourceCodeStart":248,"sourceCodeEnd":284,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableQueueConsumer.java#L248-L284","documentation":"ScalableQueueConsumer.close() waits on closeAsync().get(); if the waiting thread is interrupted, the interrupt flag is restored and a PulsarClientException with message \"Close interrupted\" is thrown. The close may still be in progress in the background — this error means the caller stopped waiting, not that close failed.","triggerScenarios":"The thread calling close() is interrupted while blocked in closeAsync().get() — typically shutdown hooks, executor shutdownNow(), or future.cancel(true) interrupting the closing thread.","commonSituations":"Graceful shutdown racing with ExecutorService.shutdownNow(); timeouts implemented via thread interruption; application kill/SIGTERM handlers interrupting consumer-close threads.","solutions":["Let shutdown complete before interrupting worker threads (order shutdown: consumers first, executors last)","Catch PulsarClientException and check that the interrupt flag is set to distinguish this case","Prefer closeAsync() with orTimeout() over interrupt-based timeouts","After catching, re-check consumer state and call closeAsync() again if needed"],"exampleFix":"// before\nexecutor.shutdownNow(); // interrupts in-flight close()\nconsumer.close();\n// after\nconsumer.close();       // close consumers first\nexecutor.shutdownNow(); // then interrupt workers\n// or: consumer.closeAsync().get(30, TimeUnit.SECONDS);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    consumer.close();\n} catch (PulsarClientException e) {\n    if (\"Close interrupted\".equals(e.getMessage())) {\n        log.warn(\"close interrupted; consumer may still be closing\");\n        consumer.closeAsync(); // confirm/finish in background\n    }\n}","preventionTips":["Order shutdown: consumers first, then interrupt threads","Use timeouts (closeAsync().get(n, TimeUnit)) instead of interruption","Never call shutdownNow() while close() is in flight"],"tags":["pulsar","close","interrupt","shutdown"],"backgroundTag":"close-interrupted","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}