refactor(mt): move context teardown order to Rust

Move ZSTDMT_freeCCtx ownership branches and teardown ordering into Rust while
keeping the MT context, worker pool, synchronization, dictionary, and custom
allocator layouts private to C callbacks.  Preserve the original factory and
round-buffer conditions, release jobs before destroying the pools, and keep
free-on-null compatible with the C API.

Test Plan:
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml free_cctx --lib
- ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
- ulimit -v 41943040; make -j1
This commit is contained in:
2026-07-20 04:49:27 +02:00
parent 6d164b7746
commit 907c3af89e
2 changed files with 322 additions and 14 deletions
+214
View File
@@ -376,6 +376,48 @@ pub struct ZSTDMT_serialStateEnsureFinishedResult {
pub type ZSTDMT_waitForJobCompleteFn = unsafe extern "C" fn(*mut c_void, c_uint, c_uint);
pub type ZSTDMT_releaseJobResourceFn = unsafe extern "C" fn(*mut c_void, c_uint);
pub type ZSTDMT_freeCCtxFn = unsafe extern "C" fn(*mut c_void);
/// Projection for MT context teardown. The context and every allocation or
/// synchronization object remain private to C; Rust owns only the ownership
/// branches and teardown ordering between callbacks.
#[repr(C)]
pub struct ZSTDMT_RustFreeCCtxState {
callback_context: *mut c_void,
provided_factory: usize,
round_buffer_present: usize,
free_factory: Option<ZSTDMT_freeCCtxFn>,
release_all_job_resources: Option<ZSTDMT_freeCCtxFn>,
free_jobs: Option<ZSTDMT_freeCCtxFn>,
free_buffer_pool: Option<ZSTDMT_freeCCtxFn>,
free_cctx_pool: Option<ZSTDMT_freeCCtxFn>,
free_seq_pool: Option<ZSTDMT_freeCCtxFn>,
free_serial_state: Option<ZSTDMT_freeCCtxFn>,
free_cdict: Option<ZSTDMT_freeCCtxFn>,
free_round_buffer: Option<ZSTDMT_freeCCtxFn>,
free_mtctx: Option<ZSTDMT_freeCCtxFn>,
}
const _: () = {
assert!(size_of::<ZSTDMT_freeCCtxFn>() == size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, callback_context) == 0);
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, provided_factory) == size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, round_buffer_present) == 2 * size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_factory) == 3 * size_of::<usize>());
assert!(
offset_of!(ZSTDMT_RustFreeCCtxState, release_all_job_resources) == 4 * size_of::<usize>()
);
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_jobs) == 5 * size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_buffer_pool) == 6 * size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_cctx_pool) == 7 * size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_seq_pool) == 8 * (usize::BITS as usize / 8));
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_serial_state) == 9 * size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_cdict) == 10 * size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_round_buffer) == 11 * size_of::<usize>());
assert!(offset_of!(ZSTDMT_RustFreeCCtxState, free_mtctx) == 12 * size_of::<usize>());
assert!(size_of::<ZSTDMT_RustFreeCCtxState>() == size_of::<[usize; 13]>());
};
type ZSTDMT_waitForLdmLockFn = unsafe extern "C" fn(*mut c_void);
type ZSTDMT_waitForLdmOverlapFn = unsafe extern "C" fn(*mut c_void, *mut c_void, usize) -> c_int;
type ZSTDMT_waitForLdmWaitFn = unsafe extern "C" fn(*mut c_void);
@@ -1597,6 +1639,60 @@ pub unsafe extern "C" fn ZSTDMT_rust_serialStateFree(
}
}
/// Run the MT context teardown order while C callbacks retain all private
/// layouts, allocators, worker pools, and synchronization details.
#[no_mangle]
pub unsafe extern "C" fn ZSTDMT_rust_freeCCtx(state: *const ZSTDMT_RustFreeCCtxState) -> usize {
if state.is_null() {
return 0;
}
let state = unsafe { &*state };
let (
Some(free_factory),
Some(release_all_job_resources),
Some(free_jobs),
Some(free_buffer_pool),
Some(free_cctx_pool),
Some(free_seq_pool),
Some(free_serial_state),
Some(free_cdict),
Some(free_round_buffer),
Some(free_mtctx),
) = (
state.free_factory,
state.release_all_job_resources,
state.free_jobs,
state.free_buffer_pool,
state.free_cctx_pool,
state.free_seq_pool,
state.free_serial_state,
state.free_cdict,
state.free_round_buffer,
state.free_mtctx,
)
else {
return 0;
};
unsafe {
if state.provided_factory == 0 {
free_factory(state.callback_context);
}
release_all_job_resources(state.callback_context);
free_jobs(state.callback_context);
free_buffer_pool(state.callback_context);
free_cctx_pool(state.callback_context);
free_seq_pool(state.callback_context);
free_serial_state(state.callback_context);
free_cdict(state.callback_context);
if state.round_buffer_present != 0 {
free_round_buffer(state.callback_context);
}
free_mtctx(state.callback_context);
}
0
}
/// C ABI entry point for the MT streaming initializer. C owns every
/// allocation, dictionary handle, synchronization object, and private context
/// mutation; this wrapper only connects those operations to the Rust policy.
@@ -3781,6 +3877,124 @@ mod tests {
static JOB_TABLE_FAIL_INIT: AtomicBool = AtomicBool::new(false);
static JOB_TABLE_TEST_LOCK: Mutex<()> = Mutex::new(());
#[derive(Default)]
struct FreeCCtxTestContext {
events: Vec<&'static str>,
}
fn record_free_cctx_event(context: *mut c_void, event: &'static str) {
unsafe {
(*context.cast::<FreeCCtxTestContext>()).events.push(event);
}
}
unsafe extern "C" fn free_cctx_test_factory(context: *mut c_void) {
record_free_cctx_event(context, "factory");
}
unsafe extern "C" fn free_cctx_test_release_jobs(context: *mut c_void) {
record_free_cctx_event(context, "release-jobs");
}
unsafe extern "C" fn free_cctx_test_jobs(context: *mut c_void) {
record_free_cctx_event(context, "jobs");
}
unsafe extern "C" fn free_cctx_test_buffer_pool(context: *mut c_void) {
record_free_cctx_event(context, "buffer-pool");
}
unsafe extern "C" fn free_cctx_test_cctx_pool(context: *mut c_void) {
record_free_cctx_event(context, "cctx-pool");
}
unsafe extern "C" fn free_cctx_test_seq_pool(context: *mut c_void) {
record_free_cctx_event(context, "seq-pool");
}
unsafe extern "C" fn free_cctx_test_serial_state(context: *mut c_void) {
record_free_cctx_event(context, "serial-state");
}
unsafe extern "C" fn free_cctx_test_cdict(context: *mut c_void) {
record_free_cctx_event(context, "cdict");
}
unsafe extern "C" fn free_cctx_test_round_buffer(context: *mut c_void) {
record_free_cctx_event(context, "round-buffer");
}
unsafe extern "C" fn free_cctx_test_mtctx(context: *mut c_void) {
record_free_cctx_event(context, "mtctx");
}
fn free_cctx_test_state(
context: &mut FreeCCtxTestContext,
provided_factory: usize,
round_buffer_present: usize,
) -> ZSTDMT_RustFreeCCtxState {
ZSTDMT_RustFreeCCtxState {
callback_context: context as *mut FreeCCtxTestContext as *mut c_void,
provided_factory,
round_buffer_present,
free_factory: Some(free_cctx_test_factory),
release_all_job_resources: Some(free_cctx_test_release_jobs),
free_jobs: Some(free_cctx_test_jobs),
free_buffer_pool: Some(free_cctx_test_buffer_pool),
free_cctx_pool: Some(free_cctx_test_cctx_pool),
free_seq_pool: Some(free_cctx_test_seq_pool),
free_serial_state: Some(free_cctx_test_serial_state),
free_cdict: Some(free_cctx_test_cdict),
free_round_buffer: Some(free_cctx_test_round_buffer),
free_mtctx: Some(free_cctx_test_mtctx),
}
}
#[test]
fn free_cctx_null_state_returns_zero_without_callbacks() {
assert_eq!(unsafe { ZSTDMT_rust_freeCCtx(ptr::null()) }, 0);
}
#[test]
fn free_cctx_preserves_callback_order_and_ownership_conditions() {
let mut context = FreeCCtxTestContext::default();
let state = free_cctx_test_state(&mut context, 0, 1);
assert_eq!(unsafe { ZSTDMT_rust_freeCCtx(&state) }, 0);
assert_eq!(
context.events,
vec![
"factory",
"release-jobs",
"jobs",
"buffer-pool",
"cctx-pool",
"seq-pool",
"serial-state",
"cdict",
"round-buffer",
"mtctx",
]
);
context.events.clear();
let state = free_cctx_test_state(&mut context, 1, 0);
assert_eq!(unsafe { ZSTDMT_rust_freeCCtx(&state) }, 0);
assert_eq!(
context.events,
vec![
"release-jobs",
"jobs",
"buffer-pool",
"cctx-pool",
"seq-pool",
"serial-state",
"cdict",
"mtctx",
]
);
}
struct MockChunkCompressor {
continue_inputs: Vec<usize>,
end_inputs: Vec<usize>,