From c7789446dce400ef7b0dbb14a53595821a96fefa Mon Sep 17 00:00:00 2001 From: ddidderr Date: Tue, 21 Jul 2026 23:37:03 +0200 Subject: [PATCH] feat(compress): move trace context reset to Rust Keep the C-owned trace slot behind an explicit pointer projection so the Rust trace boundary owns the complete enabled-call lifecycle. Rust now reads the context, publishes the record, and clears the slot after the callback; C only supplies private CCtx fields and the weak callback adapter. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo check --tests (rust) - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --tests -- -A clippy::manual-bits -D warnings (rust) - ulimit -v 41943040; make -j1 - ulimit -v 41943040; make -j1 -C tests test - Focused trace lifecycle test added; standalone cargo test remains link-blocked by the existing C-owned decompression view symbols. --- lib/compress/zstd_compress.c | 5 ++- rust/src/zstd_compress.rs | 61 ++++++++++++++++++++++++++++++++---- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/lib/compress/zstd_compress.c b/lib/compress/zstd_compress.c index 0f8d6959a..82edd360d 100644 --- a/lib/compress/zstd_compress.c +++ b/lib/compress/zstd_compress.c @@ -7362,7 +7362,7 @@ typedef void (*ZSTD_rust_traceEnd_f)( void* context, unsigned long long traceCtx, const void* trace); typedef struct { void* callbackContext; - unsigned long long traceCtx; + unsigned long long* traceCtx; int streaming; unsigned dictionaryID; size_t dictionarySize; @@ -7413,7 +7413,7 @@ void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize) #if ZSTD_TRACE ZSTD_rust_traceEndState state; state.callbackContext = cctx; - state.traceCtx = cctx->traceCtx; + state.traceCtx = &cctx->traceCtx; state.streaming = cctx->inBuffSize > 0 || cctx->outBuffSize > 0 || cctx->appliedParams.nbWorkers > 0; state.dictionaryID = cctx->dictID; @@ -7424,7 +7424,6 @@ void ZSTD_CCtx_trace(ZSTD_CCtx* cctx, size_t extraCSize) state.cctx = cctx; state.traceEnd = ZSTD_rust_traceEnd_callback; ZSTD_rust_traceEnd(&state); - cctx->traceCtx = 0; #else (void)cctx; (void)extraCSize; diff --git a/rust/src/zstd_compress.rs b/rust/src/zstd_compress.rs index 6a8d16662..ec19db9a2 100644 --- a/rust/src/zstd_compress.rs +++ b/rust/src/zstd_compress.rs @@ -2840,7 +2840,7 @@ const _: () = { #[repr(C)] pub struct ZSTD_rust_traceEndState { callback_context: *mut c_void, - trace_ctx: u64, + trace_ctx: *mut u64, streaming: c_int, dictionary_id: c_uint, dictionary_size: usize, @@ -2855,9 +2855,9 @@ const _: () = { assert!(size_of::() == size_of::()); assert!(offset_of!(ZSTD_rust_traceEndState, callback_context) == 0); assert!(offset_of!(ZSTD_rust_traceEndState, trace_ctx) == size_of::()); - assert!(offset_of!(ZSTD_rust_traceEndState, streaming) == size_of::() + size_of::()); - assert!(offset_of!(ZSTD_rust_traceEndState, dictionary_id) == size_of::() + size_of::() + size_of::()); - assert!(offset_of!(ZSTD_rust_traceEndState, dictionary_size) == size_of::() + size_of::() + 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_traceEndState, streaming) == 2 * size_of::()); + assert!(offset_of!(ZSTD_rust_traceEndState, dictionary_id) == 2 * size_of::() + size_of::()); + assert!(offset_of!(ZSTD_rust_traceEndState, dictionary_size) == 2 * size_of::() + 2 * size_of::()); assert!(offset_of!(ZSTD_rust_traceEndState, uncompressed_size) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + size_of::()); assert!(offset_of!(ZSTD_rust_traceEndState, compressed_size) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + 2 * size_of::()); assert!(offset_of!(ZSTD_rust_traceEndState, params) == offset_of!(ZSTD_rust_traceEndState, dictionary_size) + 3 * size_of::()); @@ -2872,7 +2872,10 @@ pub unsafe extern "C" fn ZSTD_rust_traceEnd(state: *const ZSTD_rust_traceEndStat let Some(state) = (unsafe { state.as_ref() }) else { return; }; - if state.trace_ctx == 0 { + let Some(trace_ctx) = (unsafe { state.trace_ctx.as_ref() }).copied() else { + return; + }; + if trace_ctx == 0 { return; } let trace = ZSTD_Trace { @@ -2890,9 +2893,10 @@ pub unsafe extern "C" fn ZSTD_rust_traceEnd(state: *const ZSTD_rust_traceEndStat unsafe { (state.trace_end)( state.callback_context, - state.trace_ctx, + trace_ctx, (&trace as *const ZSTD_Trace).cast(), ); + *state.trace_ctx = 0; } } @@ -24277,4 +24281,49 @@ mod tests { drop(Box::from_raw(state.callbacks.cast_mut())); } } + + #[derive(Default)] + struct TraceEndTestContext { + calls: usize, + trace_ctx: u64, + version: c_uint, + } + + unsafe extern "C" fn trace_end_test_callback( + context: *mut c_void, + trace_ctx: u64, + trace: *const c_void, + ) { + let context = unsafe { &mut *context.cast::() }; + context.calls += 1; + context.trace_ctx = trace_ctx; + context.version = unsafe { (*trace.cast::()).version }; + } + + #[test] + fn trace_end_publishes_once_and_clears_context() { + let mut callback_context = TraceEndTestContext::default(); + let mut trace_ctx = 42; + let state = ZSTD_rust_traceEndState { + callback_context: (&mut callback_context as *mut TraceEndTestContext).cast(), + trace_ctx: &mut trace_ctx, + streaming: 1, + dictionary_id: 7, + dictionary_size: 11, + uncompressed_size: 13, + compressed_size: 17, + params: ptr::null(), + cctx: ptr::null(), + trace_end: trace_end_test_callback, + }; + + unsafe { ZSTD_rust_traceEnd(&state) }; + assert_eq!(trace_ctx, 0); + assert_eq!(callback_context.calls, 1); + assert_eq!(callback_context.trace_ctx, 42); + assert_eq!(callback_context.version, ZSTD_VERSION_NUMBER as c_uint); + + unsafe { ZSTD_rust_traceEnd(&state) }; + assert_eq!(callback_context.calls, 1); + } }