{"record":{"id":"ab32b7c0cddc43ef","repo":"elastic/elasticsearch","slug":"zstdlib-geterrorname-hint","errorCode":null,"errorMessage":"{zstdLib.getErrorName(hint)}","messagePattern":"\\{zstdLib\\.getErrorName\\(hint\\)\\}","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"libs/native/src/main/java/org/elasticsearch/nativeaccess/Zstd.java","lineNumber":391,"sourceCode":"            // loop will keep calling us if they wanted more than one buffer worth. Capping here\n            // means we never overrun outBuf on the libzstd-side write.\n            int outRoom = Math.min(dstAvail, outBufSize);\n\n            // Copy caller's input slice into the native staging buffer at offset 0; libzstd reads\n            // from inBuf[0..srcAvail) on this call. We always feed from offset 0 (rather than\n            // tracking partial consumption inside the staging buffer) because the wrapper above\n            // re-supplies the leftover bytes on the next call.\n            if (srcAvail > 0) {\n                MemorySegment.copy(src, srcPos, inBuf, JAVA_BYTE, 0L, srcAvail);\n            }\n            SIZE_VH.set(inStruct, (long) srcAvail);\n            POS_VH.set(inStruct, 0L);\n            SIZE_VH.set(outStruct, (long) outRoom);\n            POS_VH.set(outStruct, 0L);\n\n            long hint = zstdLib.decompressStream(handle, outStruct, inStruct);\n            if (zstdLib.isError(hint)) {\n                throw new IllegalArgumentException(zstdLib.getErrorName(hint));\n            }\n\n            int srcConsumed = (int) (long) POS_VH.get(inStruct);\n            int dstProduced = (int) (long) POS_VH.get(outStruct);\n            // libzstd guarantees pos ≤ size on return — the size fields we stamped above are the\n            // upper bounds here, both already int-typed and bounded by the staging buffer sizes.\n            assert srcConsumed >= 0 && srcConsumed <= srcAvail : \"srcConsumed \" + srcConsumed + \" out of [0, \" + srcAvail + \"]\";\n            assert dstProduced >= 0 && dstProduced <= outRoom : \"dstProduced \" + dstProduced + \" out of [0, \" + outRoom + \"]\";\n            if (dstProduced > 0) {\n                MemorySegment.copy(outBuf, JAVA_BYTE, 0L, dst, dstPos, dstProduced);\n            }\n            // Translate native-staging positions back into absolute caller-array offsets — keeps\n            // the SPI contract identical to zstd-jni's \"positions are absolute in your byte[]\".\n            this.lastSrcPosAbsolute = srcPos + srcConsumed;\n            this.lastDstPosAbsolute = dstPos + dstProduced;\n            return hint;\n        }\n","sourceCodeStart":373,"sourceCodeEnd":409,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/native/src/main/java/org/elasticsearch/nativeaccess/Zstd.java#L373-L409","documentation":"Thrown inside DStream.decompress when libzstd's ZSTD_decompressStream returns an error code for the hint. This is the streaming-decompression path that stages src into a native inBuf and out into a native outBuf, then copies produced bytes back into the caller's array. An error here means the frame is corrupt mid-stream, the prior context (ZSTD_DCtx) was reset/mis-used, or the staged window is malformed. IllegalArgumentException carrying getErrorName(hint).","triggerScenarios":"Streaming a partially-read frame whose bytes were corrupted on disk or in transit; reusing a DStream across frames without proper flush/reset; feeding bytes that span two frames concatenated incorrectly; outRoom sized to 0; the in-buffer window being advanced past frame boundaries by a buggy caller.","commonSituations":"Lucene/parquet skip() operations reading into the middle of a partially-consumed frame; a network reader reusing the DStream after an earlier exception left it in a bad state; concurrent use of a single-threaded DStream context; checksum failures in the underlying storage surfacing as zstd decode errors.","solutions":["On any exception from decompressStream, discard the current DStream (close it) and create a fresh one for the next frame — do not reuse a half-broken context.","Ensure the input byte range handed to the DStream corresponds to exactly one logical frame; re-buffer at frame boundaries.","Verify outRoom > 0 (>= dStreamOutSize) before each call.","If reading from storage, cross-check the page/region CRC before blaming zstd — the bytes were likely corrupted upstream."],"exampleFix":"// before: reusing dstream after an earlier failure\nlong hint = zstdLib.decompressStream(handle, outStruct, inStruct);\n\n// after: scope dstream per frame, fail fast on error\ntry (var ds = zstd.newDStream()) {\n    ds.decompress(dst, dstPos, outRoom, src, srcPos, srcAvail);\n} catch (IllegalArgumentException e) {\n    throw new CorruptFrameException(\"zstd stream decode failed\", e);\n}","handlingStrategy":"try-catch","validationCode":"// Validate outRoom and src window before streaming decompress.\nif (outRoom <= 0) throw new IllegalArgumentException(\"outRoom must be > 0\");\nif (srcAvail <= 0) throw new IllegalArgumentException(\"no input bytes\");\n// Ensure the DStream context is fresh for this frame.","typeGuard":null,"tryCatchPattern":"try (var ds = zstd.newDStream()) {\n    ds.decompress(dst, dstPos, outRoom, src, srcPos, srcAvail);\n} catch (IllegalArgumentException e) {\n    throw new CorruptFrameException(\"zstd stream decode failed\", e);\n}","preventionTips":["Discard the DStream after any exception — never reuse a half-broken context.","Feed exactly one frame's bytes per DStream invocation cycle.","Cross-check storage CRC before blaming zstd; bytes are usually corrupted upstream."],"tags":["zstd","native","streaming","compression","codec"],"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T08:17:17.861Z"}