feat(mt): move serial wait loop into Rust

Move the serial turn wait loop out of zstdmt_compress.c and into a Rust
entry point. Rust now owns the lock, condition-wait, and ready/skip ordering
policy while C retains pthread synchronization behind lock and wait callbacks.
The projection documents that the mutex remains locked on return for the
serial advance callback, preserving the existing worker synchronization
contract. Add ABI layout checks and focused tests for both waiting and skip
paths.

Test Plan:
- cargo fmt --manifest-path rust/Cargo.toml -- --check
- ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml zstdmt_compress::tests::serial_wait_for_turn -- --nocapture
- ulimit -v 41943040 && make -j1
This commit is contained in:
2026-07-19 20:47:58 +02:00
parent b8e70c1467
commit 160fa29148
2 changed files with 155 additions and 6 deletions
+38 -6
View File
@@ -361,6 +361,24 @@ typedef void (*ZSTDMT_serialGenerateLdmFn)(
typedef void (*ZSTDMT_serialUpdateChecksumFn)(
void* opaque, const void* src, size_t srcSize);
typedef void (*ZSTDMT_serialAdvanceFn)(void* opaque);
typedef void (*ZSTDMT_serialStateLockFn)(void* opaque);
typedef void (*ZSTDMT_serialStateWaitFn)(void* opaque);
typedef struct {
void* callbackContext;
unsigned* nextJobID;
ZSTDMT_serialStateLockFn lock;
ZSTDMT_serialStateWaitFn wait;
unsigned jobID;
} ZSTDMT_RustSerialWaitForTurnState;
typedef char ZSTDMT_rust_serial_wait_for_turn_state_layout[
(offsetof(ZSTDMT_RustSerialWaitForTurnState, callbackContext) == 0
&& offsetof(ZSTDMT_RustSerialWaitForTurnState, nextJobID) == sizeof(void*)
&& offsetof(ZSTDMT_RustSerialWaitForTurnState, lock) == 2 * sizeof(void*)
&& offsetof(ZSTDMT_RustSerialWaitForTurnState, wait) == 3 * sizeof(void*)
&& offsetof(ZSTDMT_RustSerialWaitForTurnState, jobID) == 4 * sizeof(void*)
&& sizeof(ZSTDMT_RustSerialWaitForTurnState) == 5 * sizeof(void*)) ? 1 : -1];
int ZSTDMT_rust_serialStateWaitForTurn(
const ZSTDMT_RustSerialWaitForTurnState* state);
void ZSTDMT_rust_serialStateGenSequences(
ZSTDMT_RustRawSeqStore* seqStore, const void* src, size_t srcSize,
unsigned jobID, int ldmEnabled, int checksumEnabled, void* opaque,
@@ -1089,18 +1107,32 @@ static void ZSTDMT_serialState_free(SerialState* serialState)
ZSTDMT_serialState_freeTables);
}
static void ZSTDMT_serialState_lock(void* opaque)
{
SerialState* const serialState = (SerialState*)opaque;
ZSTD_PTHREAD_MUTEX_LOCK(&serialState->mutex);
}
static void ZSTDMT_serialState_wait(void* opaque)
{
SerialState* const serialState = (SerialState*)opaque;
DEBUGLOG(5, "wait for serialState->cond");
ZSTD_pthread_cond_wait(&serialState->cond, &serialState->mutex);
}
/* Rust owns the serial turn/skip decision and operation ordering. The wait
* callback intentionally leaves the main serial mutex locked; the advance
* callback releases it after Rust has performed the current turn's work. */
static int ZSTDMT_serialState_waitForTurn(void* opaque, unsigned jobID)
{
SerialState* const serialState = (SerialState*)opaque;
ZSTD_PTHREAD_MUTEX_LOCK(&serialState->mutex);
while (serialState->nextJobID < jobID) {
DEBUGLOG(5, "wait for serialState->cond");
ZSTD_pthread_cond_wait(&serialState->cond, &serialState->mutex);
}
return serialState->nextJobID == jobID;
ZSTDMT_RustSerialWaitForTurnState state;
state.callbackContext = serialState;
state.nextJobID = &serialState->nextJobID;
state.lock = ZSTDMT_serialState_lock;
state.wait = ZSTDMT_serialState_wait;
state.jobID = jobID;
return ZSTDMT_rust_serialStateWaitForTurn(&state);
}
static void ZSTDMT_serialState_generateLdm(