refactor(mt): move input publication policy to Rust

Keep the C-owned compression job callback responsible for private buffer and job-state mutation, but return the range-active decision alongside its result. Rust now owns the outer scheduler policy that publishes or suppresses the reusable input count after job creation, including the error path. The focused seam test covers the callback-clears-range case without exposing the MT context layout.

Test Plan: Not run in this atomic commit; the capped serial Rust, native, smoke, and original test-suite verification follows.
This commit is contained in:
2026-07-21 07:38:20 +02:00
parent c522906d07
commit 8373de7917
2 changed files with 144 additions and 26 deletions
+30 -12
View File
@@ -467,6 +467,16 @@ typedef struct {
size_t result;
size_t outputPos;
} ZSTDMT_RustStreamFlushResult;
typedef struct {
size_t result;
unsigned inBuffActive;
} ZSTDMT_RustStreamCreateJobResult;
typedef char ZSTDMT_stream_create_job_result_layout[
(offsetof(ZSTDMT_RustStreamCreateJobResult, result) == 0
&& offsetof(ZSTDMT_RustStreamCreateJobResult, inBuffActive)
== sizeof(size_t)
&& sizeof(ZSTDMT_RustStreamCreateJobResult) == 2 * sizeof(size_t))
? 1 : -1];
typedef struct {
size_t result;
size_t inputPos;
@@ -483,7 +493,8 @@ typedef char ZSTDMT_compress_stream_result_layout[
? 1 : -1];
typedef int (*ZSTDMT_streamTryGetInputRangeFn)(
void* opaque, ZSTDMT_RustStreamInputRangeProjection* projection);
typedef size_t (*ZSTDMT_streamCreateJobFn)(void* opaque, size_t srcSize, unsigned end);
typedef ZSTDMT_RustStreamCreateJobResult (*ZSTDMT_streamCreateJobFn)(
void* opaque, size_t srcSize, unsigned end);
typedef ZSTDMT_RustStreamFlushResult (*ZSTDMT_streamFlushProducedFn)(
void* opaque, void* outputDst, size_t outputSize, size_t outputPos,
unsigned blockToFlush, unsigned end);
@@ -3064,12 +3075,14 @@ static int ZSTDMT_tryAddCompressionJob(void* opaque, unsigned jobID)
return POOL_tryAdd(mtctx->factory, ZSTDMT_compressionJob, &mtctx->jobs[jobID]);
}
static size_t ZSTDMT_createCompressionJob(void* opaque, size_t srcSize, unsigned end)
static ZSTDMT_RustStreamCreateJobResult ZSTDMT_createCompressionJob(
void* opaque, size_t srcSize, unsigned end)
{
ZSTDMT_CCtx* const mtctx = (ZSTDMT_CCtx*)opaque;
ZSTD_EndDirective const endOp = (ZSTD_EndDirective)end;
ZSTDMT_RustCreateJobProjection projection;
ZSTDMT_RustCreateJobResult result;
ZSTDMT_RustStreamCreateJobResult streamResult;
/* 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. */
@@ -3090,13 +3103,19 @@ static size_t ZSTDMT_createCompressionJob(void* opaque, size_t srcSize, unsigned
(unsigned)mtctx->params.fParams.checksumFlag
};
result = ZSTDMT_rust_createCompressionJob(
&projection, mtctx, ZSTDMT_prepareCompressionJob,
ZSTDMT_writeEmptyCompressionJob, ZSTDMT_tryAddCompressionJob);
&projection, mtctx, ZSTDMT_prepareCompressionJob,
ZSTDMT_writeEmptyCompressionJob, ZSTDMT_tryAddCompressionJob);
/* Keep the private C mutation in this callback, but let Rust decide
* whether its local inBuffFilled value may be published afterwards. */
streamResult = (ZSTDMT_RustStreamCreateJobResult){
result.returnCode,
(unsigned)(mtctx->inBuff.buffer.start != NULL)
};
if (result.action == ZSTDMT_CREATE_JOB_TABLE_FULL) {
DEBUGLOG(5, "ZSTDMT_createCompressionJob: will not create new job : table is full");
assert((mtctx->nextJobID & mtctx->jobIDMask) == (mtctx->doneJobID & mtctx->jobIDMask));
return result.returnCode;
return streamResult;
}
mtctx->nextJobID = result.nextJobID;
@@ -3104,7 +3123,7 @@ static size_t ZSTDMT_createCompressionJob(void* opaque, size_t srcSize, unsigned
if (result.action == ZSTDMT_CREATE_JOB_EMPTY) {
DEBUGLOG(5, "ZSTDMT_createCompressionJob: creating a last empty block to end frame");
assert(endOp == ZSTD_e_end); /* only possible case : need to end the frame with an empty last block */
return result.returnCode;
return streamResult;
}
DEBUGLOG(5, "ZSTDMT_createCompressionJob: posting job %u : %u bytes (end:%u, jobNb == %u (mod:%u))",
@@ -3115,7 +3134,7 @@ static size_t ZSTDMT_createCompressionJob(void* opaque, size_t srcSize, unsigned
result.jobID);
if (result.jobReady)
DEBUGLOG(5, "ZSTDMT_createCompressionJob: no worker available for job %u", result.jobNumber);
return result.returnCode;
return streamResult;
}
/* The Rust flush state machine receives only this synchronized scalar view.
@@ -3464,11 +3483,10 @@ 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);
/* 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;
/* Rust owns the decision to publish its local count. C retains the
* private field mutation and receives either the count or zero when the
* job callback cleared the range. */
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);