feat(mt): move compression progress publication to Rust
Move the intermediate worker progress state update out of the C callback and into a Rust-owned publication leaf. The projection keeps the private job descriptor and pthread objects in C while preserving the lock, counter update, diagnostic, signal, and unlock order, including size_t wrapping behavior. Test Plan: - git diff --cached --check - capped cargo check --tests - capped cargo clippy --tests -- -A clippy::manual-bits -D warnings - capped make -j1 - capped make -j1 -C tests test - standalone capped cargo test was attempted but its hybrid link lacks the pre-existing ZSTD_rust_dctx_trace_view, ZSTD_rust_dctx_view, and ZSTD_rust_block_context_init symbols
This commit is contained in:
@@ -269,6 +269,37 @@ typedef size_t (*ZSTDMT_compressionJobSetParameterFn)(void* opaque, int value);
|
|||||||
typedef void (*ZSTDMT_compressionJobApplySequencesFn)(
|
typedef void (*ZSTDMT_compressionJobApplySequencesFn)(
|
||||||
void* opaque, void* cctx, void* sequences, size_t nbSequences);
|
void* opaque, void* cctx, void* sequences, size_t nbSequences);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void* callbackContext;
|
||||||
|
size_t* cSize;
|
||||||
|
size_t* consumed;
|
||||||
|
ZSTDMT_compressionJobVoidFn lock;
|
||||||
|
ZSTDMT_compressionJobVoidFn signal;
|
||||||
|
ZSTDMT_compressionJobVoidFn unlock;
|
||||||
|
ZSTDMT_chunkProgressFn debug;
|
||||||
|
} ZSTDMT_RustCompressionJobProgressProjection;
|
||||||
|
typedef char ZSTDMT_compression_job_progress_projection_layout[
|
||||||
|
(offsetof(ZSTDMT_RustCompressionJobProgressProjection, callbackContext)
|
||||||
|
== 0
|
||||||
|
&& offsetof(ZSTDMT_RustCompressionJobProgressProjection, cSize)
|
||||||
|
== sizeof(void*)
|
||||||
|
&& offsetof(ZSTDMT_RustCompressionJobProgressProjection, consumed)
|
||||||
|
== sizeof(void*) + sizeof(size_t)
|
||||||
|
&& offsetof(ZSTDMT_RustCompressionJobProgressProjection, lock)
|
||||||
|
== sizeof(void*) + 2 * sizeof(size_t)
|
||||||
|
&& offsetof(ZSTDMT_RustCompressionJobProgressProjection, signal)
|
||||||
|
== sizeof(void*) + 2 * sizeof(size_t) + sizeof(void*)
|
||||||
|
&& offsetof(ZSTDMT_RustCompressionJobProgressProjection, unlock)
|
||||||
|
== sizeof(void*) + 2 * sizeof(size_t) + 2 * sizeof(void*)
|
||||||
|
&& offsetof(ZSTDMT_RustCompressionJobProgressProjection, debug)
|
||||||
|
== sizeof(void*) + 2 * sizeof(size_t) + 3 * sizeof(void*)
|
||||||
|
&& sizeof(ZSTDMT_RustCompressionJobProgressProjection)
|
||||||
|
== sizeof(void*) + 2 * sizeof(size_t) + 4 * sizeof(void*))
|
||||||
|
? 1 : -1];
|
||||||
|
|
||||||
|
void ZSTDMT_rust_compressionJobProgress(
|
||||||
|
void* opaque, size_t cSize, size_t consumed);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void* callbackContext;
|
void* callbackContext;
|
||||||
size_t srcSize;
|
size_t srcSize;
|
||||||
@@ -1925,18 +1956,32 @@ typedef struct {
|
|||||||
unsigned frameChecksumNeeded; /* used only by mtctx */
|
unsigned frameChecksumNeeded; /* used only by mtctx */
|
||||||
} ZSTDMT_jobDescription;
|
} ZSTDMT_jobDescription;
|
||||||
|
|
||||||
static void ZSTDMT_compressionJobProgress(void* opaque, size_t cSize, size_t consumed)
|
static void ZSTDMT_compressionJobProgressLock(void* opaque)
|
||||||
{
|
{
|
||||||
ZSTDMT_jobDescription* const job = (ZSTDMT_jobDescription*)opaque;
|
ZSTDMT_jobDescription* const job = (ZSTDMT_jobDescription*)opaque;
|
||||||
ZSTD_PTHREAD_MUTEX_LOCK(&job->job_mutex);
|
ZSTD_PTHREAD_MUTEX_LOCK(&job->job_mutex);
|
||||||
job->cSize += cSize;
|
}
|
||||||
job->consumed = consumed;
|
|
||||||
DEBUGLOG(5, "ZSTDMT_compressionJob: compress new block : cSize==%u bytes (total: %u)",
|
static void ZSTDMT_compressionJobProgressSignal(void* opaque)
|
||||||
(U32)cSize, (U32)job->cSize);
|
{
|
||||||
|
ZSTDMT_jobDescription* const job = (ZSTDMT_jobDescription*)opaque;
|
||||||
ZSTD_pthread_cond_signal(&job->job_cond); /* warns some more data is ready to be flushed */
|
ZSTD_pthread_cond_signal(&job->job_cond); /* warns some more data is ready to be flushed */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ZSTDMT_compressionJobProgressUnlock(void* opaque)
|
||||||
|
{
|
||||||
|
ZSTDMT_jobDescription* const job = (ZSTDMT_jobDescription*)opaque;
|
||||||
ZSTD_pthread_mutex_unlock(&job->job_mutex);
|
ZSTD_pthread_mutex_unlock(&job->job_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ZSTDMT_compressionJobProgressDebug(
|
||||||
|
void* opaque, size_t cSize, size_t totalCSize)
|
||||||
|
{
|
||||||
|
(void)opaque;
|
||||||
|
DEBUGLOG(5, "ZSTDMT_compressionJob: compress new block : cSize==%u bytes (total: %u)",
|
||||||
|
(U32)cSize, (U32)totalCSize);
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ZSTDMT_jobDescription* job;
|
ZSTDMT_jobDescription* job;
|
||||||
ZSTD_CCtx_params jobParams;
|
ZSTD_CCtx_params jobParams;
|
||||||
@@ -2147,8 +2192,9 @@ static ZSTDMT_chunkProcessResult ZSTDMT_compressionJobCompress(
|
|||||||
void* opaque, unsigned lastJob)
|
void* opaque, unsigned lastJob)
|
||||||
{
|
{
|
||||||
ZSTDMT_compressionJobState* const state =
|
ZSTDMT_compressionJobState* const state =
|
||||||
(ZSTDMT_compressionJobState*)opaque;
|
(ZSTDMT_compressionJobState*)opaque;
|
||||||
ZSTDMT_jobDescription* const job = state->job;
|
ZSTDMT_jobDescription* const job = state->job;
|
||||||
|
ZSTDMT_RustCompressionJobProgressProjection progress;
|
||||||
size_t const chunkSize = 4*ZSTD_BLOCKSIZE_MAX;
|
size_t const chunkSize = 4*ZSTD_BLOCKSIZE_MAX;
|
||||||
|
|
||||||
assert(lastJob == job->lastJob);
|
assert(lastJob == job->lastJob);
|
||||||
@@ -2158,10 +2204,17 @@ static ZSTDMT_chunkProcessResult ZSTDMT_compressionJobCompress(
|
|||||||
assert(job->cSize == 0);
|
assert(job->cSize == 0);
|
||||||
assert(chunkSize > 0);
|
assert(chunkSize > 0);
|
||||||
assert((chunkSize & (chunkSize - 1)) == 0); /* chunkSize must be power of 2 for mask==(chunkSize-1) to work */
|
assert((chunkSize & (chunkSize - 1)) == 0); /* chunkSize must be power of 2 for mask==(chunkSize-1) to work */
|
||||||
|
progress.callbackContext = job;
|
||||||
|
progress.cSize = &job->cSize;
|
||||||
|
progress.consumed = &job->consumed;
|
||||||
|
progress.lock = ZSTDMT_compressionJobProgressLock;
|
||||||
|
progress.signal = ZSTDMT_compressionJobProgressSignal;
|
||||||
|
progress.unlock = ZSTDMT_compressionJobProgressUnlock;
|
||||||
|
progress.debug = ZSTDMT_compressionJobProgressDebug;
|
||||||
return ZSTDMT_rust_compressJobChunks(
|
return ZSTDMT_rust_compressJobChunks(
|
||||||
state->cctx, job->src.start, job->src.size,
|
state->cctx, job->src.start, job->src.size,
|
||||||
state->dstBuff.start, state->dstBuff.capacity, chunkSize, lastJob,
|
state->dstBuff.start, state->dstBuff.capacity, chunkSize, lastJob,
|
||||||
job, ZSTDMT_compressionJobProgress);
|
&progress, ZSTDMT_rust_compressionJobProgress);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ZSTDMT_compressionJobTrace(void* opaque)
|
static void ZSTDMT_compressionJobTrace(void* opaque)
|
||||||
|
|||||||
@@ -534,6 +534,77 @@ pub type ZSTDMT_compressionJobSetParameterFn = unsafe extern "C" fn(*mut c_void,
|
|||||||
pub type ZSTDMT_compressionJobApplySequencesFn =
|
pub type ZSTDMT_compressionJobApplySequencesFn =
|
||||||
unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, usize);
|
unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, usize);
|
||||||
|
|
||||||
|
/// Live fields and synchronization callbacks for one worker's intermediate
|
||||||
|
/// compression progress. The private job descriptor and pthread objects
|
||||||
|
/// remain in C; Rust owns the publication order and wrapping counter update.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ZSTDMT_compressionJobProgressProjection {
|
||||||
|
pub callbackContext: *mut c_void,
|
||||||
|
pub cSize: *mut usize,
|
||||||
|
pub consumed: *mut usize,
|
||||||
|
pub lock: Option<ZSTDMT_compressionJobVoidFn>,
|
||||||
|
pub signal: Option<ZSTDMT_compressionJobVoidFn>,
|
||||||
|
pub unlock: Option<ZSTDMT_compressionJobVoidFn>,
|
||||||
|
pub debug: Option<ZSTDMT_chunkProgressFn>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTDMT_compressionJobProgressProjection, callbackContext) == 0
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
offset_of!(ZSTDMT_compressionJobProgressProjection, cSize) == size_of::<*mut c_void>()
|
||||||
|
);
|
||||||
|
assert!(offset_of!(ZSTDMT_compressionJobProgressProjection, consumed)
|
||||||
|
== size_of::<*mut c_void>() + size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTDMT_compressionJobProgressProjection, lock)
|
||||||
|
== size_of::<*mut c_void>() + 2 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTDMT_compressionJobProgressProjection, signal)
|
||||||
|
== size_of::<*mut c_void>() + 2 * size_of::<usize>() + size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTDMT_compressionJobProgressProjection, unlock)
|
||||||
|
== size_of::<*mut c_void>() + 2 * size_of::<usize>() + 2 * size_of::<usize>());
|
||||||
|
assert!(offset_of!(ZSTDMT_compressionJobProgressProjection, debug)
|
||||||
|
== size_of::<*mut c_void>() + 2 * size_of::<usize>() + 3 * size_of::<usize>());
|
||||||
|
assert!(size_of::<ZSTDMT_compressionJobProgressProjection>()
|
||||||
|
== size_of::<*mut c_void>() + 2 * size_of::<usize>() + 4 * size_of::<usize>());
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Publish one intermediate worker block while preserving the C callback's
|
||||||
|
/// lock, update, diagnostic, signal, and unlock order.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn ZSTDMT_rust_compressionJobProgress(
|
||||||
|
opaque: *mut c_void,
|
||||||
|
c_size: usize,
|
||||||
|
consumed: usize,
|
||||||
|
) {
|
||||||
|
let Some(state) = (unsafe {
|
||||||
|
opaque
|
||||||
|
.cast::<ZSTDMT_compressionJobProgressProjection>()
|
||||||
|
.as_ref()
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let (Some(lock), Some(signal), Some(unlock)) = (state.lock, state.signal, state.unlock)
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if state.cSize.is_null() || state.consumed.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
lock(state.callbackContext);
|
||||||
|
*state.cSize = (*state.cSize).wrapping_add(c_size);
|
||||||
|
*state.consumed = consumed;
|
||||||
|
if let Some(debug) = state.debug {
|
||||||
|
debug(state.callbackContext, c_size, *state.cSize);
|
||||||
|
}
|
||||||
|
signal(state.callbackContext);
|
||||||
|
unlock(state.callbackContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
struct ZSTDMT_compressionJobSequenceProjection {
|
struct ZSTDMT_compressionJobSequenceProjection {
|
||||||
cctx: *mut c_void,
|
cctx: *mut c_void,
|
||||||
@@ -6207,6 +6278,12 @@ mod tests {
|
|||||||
calls: Vec<(usize, usize)>,
|
calls: Vec<(usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MockJobProgress {
|
||||||
|
events: Vec<&'static str>,
|
||||||
|
debug_calls: Vec<(usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct MockCompressionJob {
|
struct MockCompressionJob {
|
||||||
events: Vec<&'static str>,
|
events: Vec<&'static str>,
|
||||||
@@ -8349,6 +8426,28 @@ mod tests {
|
|||||||
progress.calls.push((c_size, consumed));
|
progress.calls.push((c_size, consumed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn mock_job_progress_lock(context: *mut c_void) {
|
||||||
|
unsafe { (*context.cast::<MockJobProgress>()).events.push("lock") };
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn mock_job_progress_signal(context: *mut c_void) {
|
||||||
|
unsafe { (*context.cast::<MockJobProgress>()).events.push("signal") };
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn mock_job_progress_unlock(context: *mut c_void) {
|
||||||
|
unsafe { (*context.cast::<MockJobProgress>()).events.push("unlock") };
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn mock_job_progress_debug(
|
||||||
|
context: *mut c_void,
|
||||||
|
c_size: usize,
|
||||||
|
total_c_size: usize,
|
||||||
|
) {
|
||||||
|
let progress = unsafe { &mut *context.cast::<MockJobProgress>() };
|
||||||
|
progress.events.push("debug");
|
||||||
|
progress.debug_calls.push((c_size, total_c_size));
|
||||||
|
}
|
||||||
|
|
||||||
fn run_mock_chunk_loop(
|
fn run_mock_chunk_loop(
|
||||||
compressor: &mut MockChunkCompressor,
|
compressor: &mut MockChunkCompressor,
|
||||||
progress: &mut MockChunkProgress,
|
progress: &mut MockChunkProgress,
|
||||||
@@ -8375,6 +8474,37 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compression_job_progress_preserves_publication_order_and_wraps_c_size() {
|
||||||
|
let mut c_size = usize::MAX - 1;
|
||||||
|
let mut consumed = 0;
|
||||||
|
let mut progress = MockJobProgress::default();
|
||||||
|
let projection = ZSTDMT_compressionJobProgressProjection {
|
||||||
|
callbackContext: (&mut progress as *mut MockJobProgress).cast(),
|
||||||
|
cSize: &mut c_size,
|
||||||
|
consumed: &mut consumed,
|
||||||
|
lock: Some(mock_job_progress_lock),
|
||||||
|
signal: Some(mock_job_progress_signal),
|
||||||
|
unlock: Some(mock_job_progress_unlock),
|
||||||
|
debug: Some(mock_job_progress_debug),
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
ZSTDMT_rust_compressionJobProgress(
|
||||||
|
(&projection as *const ZSTDMT_compressionJobProgressProjection)
|
||||||
|
.cast_mut()
|
||||||
|
.cast(),
|
||||||
|
3,
|
||||||
|
17,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(c_size, 1);
|
||||||
|
assert_eq!(consumed, 17);
|
||||||
|
assert_eq!(progress.events, ["lock", "debug", "signal", "unlock"]);
|
||||||
|
assert_eq!(progress.debug_calls, [(3, 1)]);
|
||||||
|
}
|
||||||
|
|
||||||
fn run_output_publication(
|
fn run_output_publication(
|
||||||
output: &mut [u8],
|
output: &mut [u8],
|
||||||
output_size: usize,
|
output_size: usize,
|
||||||
|
|||||||
Reference in New Issue
Block a user