feat(mt): move frame progression job aggregation into Rust

Move the multithreaded frame-progression ring scan into Rust so the Rust
scheduler owns job-ID masking, ready-job range handling, error-output
normalization, and active-worker accounting. Keep C-owned job descriptors and
mutex-protected snapshots behind the narrow ZSTDMT_projectJob callback, which
preserves the private synchronization boundary while making the aggregation
policy directly testable in Rust.

Test Plan:
- cargo fmt --manifest-path rust/Cargo.toml -- --check
- ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --lib (691 passed)
- ulimit -v 41943040 && CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- ulimit -v 41943040 && MAKEFLAGS=-j1 make -B -C programs -j1 zstd
- ulimit -v 41943040 && MAKEFLAGS=-j1 make -C tests -j1 test-zstream ZSTREAM_TESTTIME=-T1s (84 tests and both short fuzz rounds passed)
This commit is contained in:
2026-07-19 18:53:08 +02:00
parent fec11ae7d0
commit adaae03552
3 changed files with 141 additions and 23 deletions
+126
View File
@@ -1957,6 +1957,43 @@ fn frame_progression_add_job(
progression
}
#[inline]
fn frame_progression_with_jobs<F>(
consumed: u64,
in_buff_filled: usize,
produced: u64,
current_job_id: c_uint,
done_job_id: c_uint,
next_job_id: c_uint,
job_ready: c_uint,
job_id_mask: c_uint,
mut project_job: F,
) -> ZSTD_frameProgression
where
F: FnMut(c_uint) -> ZSTDMT_jobProjection,
{
let mut progression = frame_progression(consumed, in_buff_filled, produced, current_job_id);
debug_assert!(job_ready <= 1);
let last_job_id = next_job_id.wrapping_add(job_ready);
for job_id in done_job_id..last_job_id {
let projection = project_job(job_id & job_id_mask);
let (produced, flushed) = if ERR_isError(projection.cSize) {
(0, 0)
} else {
(projection.cSize, projection.dstFlushed)
};
debug_assert!(flushed <= produced);
progression = frame_progression_add_job(
progression,
projection.srcSize,
projection.consumed,
produced,
flushed,
);
}
progression
}
/// Construct the base MT frame progression from C-owned scalar state.
#[no_mangle]
pub extern "C" fn ZSTDMT_rust_frameProgression(
@@ -1980,6 +2017,42 @@ pub extern "C" fn ZSTDMT_rust_frameProgressionAddJob(
frame_progression_add_job(progression, src_size, consumed, produced, flushed)
}
/// Aggregate the base MT progression with mutex-protected C job snapshots.
/// Rust owns only the ring scan and normalization policy; C retains the job
/// descriptor layout and takes each descriptor mutex in `projectJob`.
#[no_mangle]
pub unsafe extern "C" fn ZSTDMT_rust_frameProgressionWithJobs(
consumed: u64,
in_buff_filled: usize,
produced: u64,
current_job_id: c_uint,
done_job_id: c_uint,
next_job_id: c_uint,
job_ready: c_uint,
job_id_mask: c_uint,
opaque: *mut c_void,
project_job: Option<ZSTDMT_jobProjectionFn>,
) -> ZSTD_frameProgression {
let Some(project_job) = project_job else {
return frame_progression(consumed, in_buff_filled, produced, current_job_id);
};
frame_progression_with_jobs(
consumed,
in_buff_filled,
produced,
current_job_id,
done_job_id,
next_job_id,
job_ready,
job_id_mask,
|job_id| {
let mut projection = ZSTDMT_jobProjection::default();
unsafe { project_job(opaque, job_id, &mut projection) };
projection
},
)
}
#[inline]
unsafe fn rolling_hash_append(mut hash: u64, input: *const u8, size: usize) -> u64 {
for pos in 0..size {
@@ -4469,6 +4542,59 @@ mod tests {
);
}
#[test]
fn frame_progression_with_jobs_masks_slots_and_normalizes_errors() {
let projections = [
ZSTDMT_jobProjection {
consumed: 50,
cSize: 8,
srcSize: 50,
dstFlushed: 8,
..ZSTDMT_jobProjection::default()
},
ZSTDMT_jobProjection {
consumed: 17,
cSize: 42,
srcSize: 80,
dstFlushed: 10,
..ZSTDMT_jobProjection::default()
},
ZSTDMT_jobProjection {
consumed: 30,
cSize: ERROR(ZstdErrorCode::SequenceProducerFailed),
srcSize: 30,
dstFlushed: 29,
..ZSTDMT_jobProjection::default()
},
ZSTDMT_jobProjection {
consumed: 5,
cSize: 7,
srcSize: 40,
dstFlushed: 6,
..ZSTDMT_jobProjection::default()
},
];
let mut calls = Vec::new();
let progression = frame_progression_with_jobs(100, 20, 300, 9, 5, 8, 1, 3, |job_id| {
calls.push(job_id);
projections[job_id as usize]
});
assert_eq!(calls, [1, 2, 3, 0]);
assert_eq!(
progression,
ZSTD_frameProgression {
ingested: 320,
consumed: 202,
produced: 357,
flushed: 324,
currentJobID: 9,
nbActiveWorkers: 2,
}
);
}
#[test]
fn input_data_in_use_skips_first_round_and_scans_oldest_ring_slot() {
let source_a = [0u8; 8];