{"record":{"id":"dd3049ce0a6a5483","repo":"apache/pulsar","slug":"no-segment-covers-hash-hash-for-key-key","errorCode":null,"errorMessage":"No segment covers hash + hash + for key: + key","messagePattern":"No segment covers hash \\+ hash \\+ for key: \\+ key","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/SegmentRouter.java","lineNumber":66,"sourceCode":"     * @param key the message key\n     * @param activeSegments the currently active segments (sorted by hash range)\n     * @return the segment ID to route to\n     * @throws IllegalStateException if no segment covers the hash\n     */\n    long route(String key, List<ActiveSegment> activeSegments) {\n        if (activeSegments.isEmpty()) {\n            throw new IllegalStateException(\"No active segments\");\n        }\n        if (allLegacy(activeSegments)) {\n            return routeModN(key, activeSegments);\n        }\n        int hash = hash(key);\n        for (var segment : activeSegments) {\n            if (segment.hashRange().contains(hash)) {\n                return segment.segmentId();\n            }\n        }\n        throw new IllegalStateException(\"No segment covers hash \" + hash + \" for key: \" + key);\n    }\n\n    /**\n     * Route a message without a key using round-robin across active segments.\n     */\n    long routeRoundRobin(List<ActiveSegment> activeSegments) {\n        if (activeSegments.isEmpty()) {\n            throw new IllegalStateException(\"No active segments\");\n        }\n        int idx = Math.abs(roundRobinCounter.getAndIncrement() % activeSegments.size());\n        return activeSegments.get(idx).segmentId();\n    }\n\n    /** True iff every active segment is a legacy segment — signals a synthetic-layout topic. */\n    private static boolean allLegacy(List<ActiveSegment> activeSegments) {\n        for (var s : activeSegments) {\n            if (!s.isLegacy()) {\n                return false;","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-client-v5/src/main/java/org/apache/pulsar/client/impl/v5/SegmentRouter.java#L48-L84","documentation":"SegmentRouter.route() throws IllegalStateException when no active segment's hashRange contains the computed hash of the key, i.e. the hash ring has a coverage gap. The exception message includes the numeric hash and the original key for diagnosis.","triggerScenarios":"route(key, activeSegments) where activeSegments is non-empty but none of their hashRanges contains hash(key) — typically an inconsistent or partially delivered layout after a split/merge.","commonSituations":"Mid-transition layouts where a split has sealed the parent segment before child segments are active; mixed-version clients building hash ranges; manual layout construction missing a range.","solutions":["Refresh the segment layout (wait for the next DAG watch update) — the gap is usually transient.","Check hash-range construction: ranges must fully cover the hash space with no gaps after split/merge.","If persistent, validate layout integrity before use (assert contiguous full coverage) and reject invalid layouts.","Report the key/hash from the message — a bug in hashRange.contains or hash() may need a library fix."],"exampleFix":"// before\nlong seg = router.route(key, segments); // gap -> IllegalStateException\n// after\nvalidateCoverage(segments); // assert union of hashRanges covers full space\nlong seg = router.route(key, segments);","handlingStrategy":"validation","validationCode":"// ensure hash ranges fully cover the hash space before routing\nint covered = 0;\nfor (var s : segments) { covered |= s.hashRange().coverageMask(); } // domain-specific check\n// or simply refresh the layout if a gap is detected","typeGuard":"static boolean layoutCoversAll(List<SegmentRouter.ActiveSegment> s) {\n    // verify union of hashRanges has no gaps after sorting by range start\n    return s.stream().map(SegmentRouter.ActiveSegment::hashRange)\n        .sorted(Comparator.comparing(HashRange::start))\n        .reduce((a, b) -> b.start() <= a.end() + 1 ? b : null).isPresent(); // simplified\n}","tryCatchPattern":"try {\n    long seg = router.route(key, activeSegments);\n} catch (IllegalStateException e) {\n    refreshLayout(); // transient gap: wait for next DAG watch update\n}","preventionTips":["Validate that split/merge layouts remain contiguous with no coverage gaps","Refresh the layout when a coverage gap is detected — gaps are usually transient","Keep clients on matching library versions so hash-range construction agrees"],"tags":["routing","hash-range","segment-layout","illegal-state"],"backgroundTag":"hash-range-coverage-gap","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"}