{"record":{"id":"9913379032912832","repo":"apache/pulsar","slug":"cannot-prune-an-active-segment-segmentid","errorCode":null,"errorMessage":"Cannot prune an active segment: ${segmentId}","messagePattern":"Cannot prune an active segment: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java","lineNumber":307,"sourceCode":"\n        return new SegmentLayout(newEpoch, nextSegmentId + 1, newSegments);\n    }\n\n    /**\n     * Prune an expired segment from the DAG. The segment must be sealed and have no\n     * children that are still in the DAG (i.e., children have already been pruned or\n     * the segment is a leaf that was sealed).\n     *\n     * @param segmentId the segment to prune\n     * @return a new SegmentLayout with the segment removed\n     */\n    public SegmentLayout pruneSegment(long segmentId) {\n        SegmentInfo segment = allSegments.get(segmentId);\n        if (segment == null) {\n            throw new IllegalArgumentException(\"Segment not found: \" + segmentId);\n        }\n        if (segment.isActive()) {\n            throw new IllegalArgumentException(\"Cannot prune an active segment: \" + segmentId);\n        }\n\n        Map<Long, SegmentInfo> newSegments = new LinkedHashMap<>(allSegments);\n        newSegments.remove(segmentId);\n\n        // Remove this segment from its children's parent lists\n        for (long childId : segment.childIds()) {\n            SegmentInfo child = newSegments.get(childId);\n            if (child != null) {\n                List<Long> newParentIds = child.parentIds().stream()\n                        .filter(id -> id != segmentId)\n                        .collect(Collectors.toList());\n                newSegments.put(childId, child.withParentIds(newParentIds));\n            }\n        }\n\n        // Remove this segment from its parents' child lists\n        for (long parentId : segment.parentIds()) {","sourceCodeStart":289,"sourceCodeEnd":325,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/pulsar-broker/src/main/java/org/apache/pulsar/broker/service/scalable/SegmentLayout.java#L289-L325","documentation":"pruneSegment may only remove segments that are sealed (not active); pruning an active segment would delete live data ownership. If segment.isActive() it throws IllegalArgumentException(\"Cannot prune an active segment: <id>\").","triggerScenarios":"Calling pruneSegment on an ID that is still an active serving segment — e.g. iterating all segments instead of only expired/sealed ones, or pruning against a stale layout where the segment has since become active again (state restored after failure).","commonSituations":"Custom retention/prune logic without an expiration check; tests calling testCannotPruneActiveSegment-style negative paths; prune logic racing a recovery that reactivated the segment.","solutions":["Only prune segments that are sealed AND expired past retention: check !segment.isActive() plus the expiry timestamp before calling","Derive prune candidates from the sealed segments in the layout, never from allSegments","Re-check activity on the freshest layout immediately before each prune to avoid racing a recovery","Let pruneAllAsync drive candidate selection instead of hand-picking IDs"],"exampleFix":"// before\nlayout.pruneSegment(id);\n// after\nSegmentInfo seg = layout.getAllSegments().get(id);\nif (seg != null && !seg.isActive() && isExpired(seg, nowMs)) {\n    layout.pruneSegment(id);\n}","handlingStrategy":"validation","validationCode":"boolean canPrune(SegmentLayout layout, long segmentId, long nowMs) {\n    var seg = layout.getAllSegments().get(segmentId);\n    return seg != null && !seg.isActive() && isExpired(seg, nowMs);\n}","typeGuard":"boolean isSealedSegment(SegmentLayout layout, long id) {\n    var seg = layout.getAllSegments().get(id);\n    return seg != null && !seg.isActive();\n}","tryCatchPattern":"try {\n    newLayout = layout.pruneSegment(id);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Cannot prune an active segment\")) {\n        // segment is live (or reactivated) — skip it this round\n        return;\n    }\n    throw e;\n}","preventionTips":["Apply an explicit expiry (sealed-at + retention) check before pruning","Prefer pruneAllAsync's candidate selection over hand-picked IDs","Re-verify sealed status on the freshest snapshot immediately before the CAS","Skip-and-continue policy for active segments in prune loops"],"tags":["java","illegal-argument","segment-state","prune"],"backgroundTag":"segment-not-sealed","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"}