{"record":{"id":"8bd39eabba99a0d6","repo":"apache/pulsar","slug":"cursor-s-mark-delete-position-s-is-ahead-of-the","errorCode":null,"errorMessage":"Cursor %s mark-delete position %s is ahead of the last position %s for managed ledger %s","messagePattern":"Cursor (.+?) mark-delete position (.+?) is ahead of the last position (.+?) for managed ledger (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedCursorImpl.java","lineNumber":1289,"sourceCode":"\n    @Override\n    public int getNonContiguousDeletedMessagesRangeSerializedSize() {\n        return this.individualDeletedMessagesSerializedSize;\n    }\n\n    @Override\n    public long getEstimatedSizeSinceMarkDeletePosition() {\n        Position markDeletePosition = this.markDeletePosition;\n        Position lastPosition = ledger.getLastPosition();\n        if (markDeletePosition == null || markDeletePosition.compareTo(lastPosition) == 0) {\n            return 0;\n        }\n        if (markDeletePosition.compareTo(lastPosition) > 0) {\n            if (!ledger.ledgerExists(lastPosition.getLedgerId())\n                    || isMarkDeletePositionOnEmptyCurrentLedger(markDeletePosition)) {\n                return 0;\n            }\n            throw new IllegalArgumentException(String.format(\n                    \"Cursor %s mark-delete position %s is ahead of the last position %s for managed ledger %s\",\n                    name, markDeletePosition, lastPosition, ledger.getName()));\n        }\n\n        long totalSize = ledger.estimateBacklogFromPosition(markDeletePosition);\n\n        // Need to subtract size of individual deleted messages\n        log.debug()\n                .attr(\"markDeletePosition\", markDeletePosition)\n                .attr(\"totalSize\", totalSize)\n                .log(\"Calculating backlog size\");\n\n        // Get count of individually deleted entries in the backlog range\n        long deletedCount = 0;\n        lock.readLock().lock();\n        try {\n            Range<Position> backlogRange = Range.openClosed(markDeletePosition, lastPosition);\n            deletedCount = individualDeletedMessages.cardinality(","sourceCodeStart":1271,"sourceCodeEnd":1307,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/impl/ManagedCursorImpl.java#L1271-L1307","documentation":"StreamingDataBlockHeaderImpl.fromStream reads the leading 4-byte magic word from a data block header read back from tiered storage and throws this IOException when it does not equal the expected MAGIC_WORD. The library uses the magic word as a integrity/format marker for offloaded ledger data blocks. A mismatch means the stream is not positioned at a valid data block header or the stored data is corrupted, truncated, or written by an incompatible format version.","triggerScenarios":"Calling StreamingDataBlockHeaderImpl.fromStream(InputStream) on a stream whose first 4 bytes are not the expected MAGIC_WORD — e.g. the InputStream is positioned past the header, points at the wrong offset in the blob, the blob content is corrupted/overwritten, or the stream yields bytes in the wrong order (endianness/decoding issue).","commonSituations":"Reading an offloaded ledger object from object storage (S3/GCS/Azure) that was truncated by a failed upload, manually edited or re-uploaded, or read from the wrong blob/key; restoring with a BookKeeper/Pulsar version whose block format differs; buggy custom offload code that passes a mid-file stream instead of the block start.","solutions":["Verify the stream is positioned exactly at the start of a data block before calling fromStream (reset/reopen the InputStream at offset 0 of the block).","Re-offload the ledger to a new location — the stored blob is likely corrupted or truncated; do not attempt to hand-patch the magic word.","Check that the reader and writer use compatible BookKeeper/Pulsar versions for the streaming offload format.","Inspect the actual bytes read (first 4 bytes) against MAGIC_WORD to confirm whether it is an offset problem (valid magic elsewhere in the stream) vs corruption (garbage bytes)."],"exampleFix":"// before: stream may be positioned mid-block\nInputStream in = blob.getPayload().openStream();\nin.skip(someOffset);\nStreamingDataBlockHeaderImpl.fromStream(in);\n\n// after: ensure stream is at block start\ntry (InputStream in = blob.getPayload().openStream()) {\n    StreamingDataBlockHeaderImpl.fromStream(in); // reads from offset 0\n}","handlingStrategy":"try-catch","validationCode":"if (available(stream) < 4) { throw new IOException(\"Stream too short for data block header\"); }","typeGuard":null,"tryCatchPattern":"try {\n    StreamingDataBlockHeader header = StreamingDataBlockHeaderImpl.fromStream(in);\n} catch (IOException e) {\n    if (e.getMessage().contains(\"magic word\")) {\n        log.warn(\"Corrupt or misaligned offloaded block header\");\n        // re-offload or fail the read-back, do not retry same stream\n    }\n    throw e;\n}","preventionTips":["Always read the header from the very start of a block; never pass a partially consumed stream.","Verify blob integrity (checksum/length) at download time before parsing.","Pin matching BookKeeper/Pulsar versions between offload writer and reader.","Treat magic mismatches as data corruption: re-offload, don't hand-edit bytes."],"tags":["io","corruption","offload","bookkeeper"],"backgroundTag":"magic-word-mismatch","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"}