From a57e0f83e18fb7f7553ec491734429af64d0d651 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 20 Jul 2026 15:17:11 +0200 Subject: [PATCH] fix(mt): preserve range state across job callback The first MT state-publication fix made the C count current before job construction, but the Rust result still unconditionally copied its local pre-job fill count back into `mtctx` after the callback returned. Preparing a job clears `mtctx->inBuff.buffer` and `mtctx->inBuff.filled`, so the next stream pass then observed a consumed range as if it were still active and returned a generic error in the multi-file CLI test. Keep the C-cleared state whenever job preparation has detached the active range. Rust's filled count is published only while the C range remains active, which covers the blocked/table-full path where no job reset occurred. The callback still receives the current count before preparation, and the C90 projection declaration is kept before executable statements. Test Plan: - `cc -fsyntax-only -Werror=incompatible-pointer-types -Ilib -Ilib/common -Ilib/compress -Ilib/decompress -Ilib/dict -Ilib/legacy lib/compress/zstdmt_compress.c` -- passed - `git diff --check` and `git diff --cached --check` -- passed - The capped upstream suite reproduced a generic error in the multi-file CLI path before this correction; the full rerun is pending --- lib/compress/zstdmt_compress.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index d0e951630..cd0cd89d2 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -2723,12 +2723,13 @@ static size_t ZSTDMT_createCompressionJob(void* opaque, size_t srcSize, unsigned { ZSTDMT_CCtx* const mtctx = (ZSTDMT_CCtx*)opaque; ZSTD_EndDirective const endOp = (ZSTD_EndDirective)end; + ZSTDMT_RustCreateJobProjection projection; /* Rust fills the reusable input range in its local projection before * calling this C-owned job-construction callback. Publish that count * before the callback prepares a job and checks the C context. */ assert(srcSize <= mtctx->inBuff.buffer.capacity); mtctx->inBuff.filled = srcSize; - ZSTDMT_RustCreateJobProjection const projection = { + projection = (ZSTDMT_RustCreateJobProjection){ mtctx->doneJobID, mtctx->nextJobID, mtctx->jobIDMask, @@ -3099,7 +3100,11 @@ size_t ZSTDMT_compressStream_generic(ZSTDMT_CCtx* mtctx, (U32)endOp, (U32)(input->size - input->pos)); assert(output->pos <= output->size); assert(input->pos <= input->size); - mtctx->inBuff.filled = result.inBuffFilled; + /* A prepared job clears the C range while Rust still has the pre-job + * count in its local projection. Only publish the result when the range + * remains active, which is the no-job-created path. */ + if (mtctx->inBuff.buffer.start != NULL) + mtctx->inBuff.filled = result.inBuffFilled; input->pos = result.inputPos; output->pos = result.outputPos; DEBUGLOG(5, "end of ZSTDMT_compressStream_generic: remainingToFlush = %u", (U32)result.result);