{"record":{"id":"d4b32da1e7c05fb7","repo":"apache/pulsar","slug":"interrupted-while-waiting-for-layout-update","errorCode":null,"errorMessage":"Interrupted while waiting for layout update","messagePattern":"Interrupted while waiting for layout update","errorType":"exception","errorClass":"PulsarClientException","httpStatus":null,"severity":"error","filePath":"pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableTopicProducer.java","lineNumber":234,"sourceCode":"            } catch (org.apache.pulsar.client.api.PulsarClientException e) {\n                // Thrown by the v4 producer's send().\n                if (!isSegmentGoneError(e)) {\n                    throw new PulsarClientException(e.getMessage(), e);\n                }\n                lastError = new PulsarClientException(e.getMessage(), e);\n            }\n            // The target segment is gone: sealed by a split/merge, or terminated by a\n            // regular-to-scalable migration. Drop the stale per-segment producer and wait\n            // for the DAG watch to deliver the new layout; routeMessage on the next attempt\n            // lands on an active child.\n            log.info().attr(\"segmentId\", segmentId).attr(\"attempt\", attempt + 1)\n                    .log(\"Target segment gone, waiting for layout update\");\n            segmentProducers.remove(segmentId);\n            try {\n                Thread.sleep(Math.min(100L * (attempt + 1), SEND_RETRY_MAX_BACKOFF_MS));\n            } catch (InterruptedException ie) {\n                Thread.currentThread().interrupt();\n                throw new PulsarClientException(\"Interrupted while waiting for layout update\", ie);\n            }\n        }\n        throw lastError != null ? lastError\n                : new PulsarClientException(\"Failed to send after segment termination retries\");\n    }\n\n    /**\n     * True if {@code t} (or one of its causes) signals that the target segment is gone —\n     * sealed by a split/merge or terminated by a regular-to-scalable migration — so the send\n     * should be retried once the new layout arrives. Handles both the v4 exceptions thrown by\n     * {@code send()} and the V5-wrapped exceptions thrown while (re)creating the per-segment\n     * producer on a now-terminated topic.\n     */\n    private static boolean isSegmentGoneError(Throwable t) {\n        for (Throwable cause = t; cause != null; cause = cause.getCause()) {\n            if (cause instanceof org.apache.pulsar.client.api.PulsarClientException.TopicTerminatedException) {\n                return true;\n            }","sourceCodeStart":216,"sourceCodeEnd":252,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/ScalableTopicProducer.java#L216-L252","documentation":"When the target segment is gone (sealed by split/merge or topic migration), sendInternal drops the stale per-segment producer and sleeps with linear backoff (capped at SEND_RETRY_MAX_BACKOFF_MS) waiting for the DAG watch to deliver a new layout. If the sleeping thread is interrupted, the interrupt flag is restored and this PulsarClientException is thrown.","triggerScenarios":"Sending to a scalable topic right after a segment split/merge or regular-to-scalable migration, while the sending thread is interrupted during the layout-wait sleep.","commonSituations":"Shutdown or task cancellation exactly during a layout transition; test harnesses interrupting producer threads; slow DAG watch delivery combined with aggressive cancellation.","solutions":["Do not interrupt producer send threads; rely on producer.closeAsync() for cancellation.","If cancellation is required, catch this exception and re-send after the new layout is observed.","Check DAG watch/layout delivery latency if interruption timeouts are chronically hit during migrations.","Restore handling: the interrupt flag is re-set, so the surrounding task should terminate promptly."],"exampleFix":"// before\nThread cancel: task.interrupt() while producer.send(msg) is retrying\n// after\nproducer.closeAsync().thenRun(task::cancel); // cancel via producer lifecycle, not interrupt","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    producer.send(msg);\n} catch (PulsarClientException e) {\n    if (e.getCause() instanceof InterruptedException) {\n        Thread.currentThread().interrupt();\n        return; // abandon send, layout will settle\n    }\n    throw e;\n}","preventionTips":["Do not interrupt threads while sends are in flight; cancel via producer.closeAsync()","Expect layout-wait windows after segment split/merge or migration","Keep DAG watch delivery fast so retry backoff rarely blocks"],"tags":["interruption","segment-layout","retry","pulsar-client"],"backgroundTag":"thread-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"}