From dc83c98f0605a50f66f688908437fb6742e0afa4 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 20 Jul 2026 04:38:09 +0200 Subject: [PATCH] refactor(fileio): move compression teardown order to Rust Keep fileio's compression-resource teardown order in the Rust policy layer: dictionary, write pool, read pool, then compression context. The C adapter keeps cRess_t and each private resource layout local while exposing only one callback projection to Rust, preserving the existing cleanup behavior. The Rust entry point also treats a null state as a no-op and verifies the projection layout at compile time. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/cli/Cargo.toml free_c_resources --lib - ulimit -v 41943040; make -j1 - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/cli/Cargo.toml --all-targets - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings --- programs/fileio.c | 57 +++++++++++++++++++++++-- rust/src/fileio_prefs.rs | 90 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 5 deletions(-) diff --git a/programs/fileio.c b/programs/fileio.c index b806ce72c..0acef6e8b 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -1820,12 +1820,61 @@ static cRess_t FIO_createCResources(FIO_prefs_t* const prefs, return ress; } +typedef void (*FIO_rust_freeCResourcesCallback_f)(void* context); +typedef struct { + void* callbackContext; + FIO_rust_freeCResourcesCallback_f freeDict; + FIO_rust_freeCResourcesCallback_f freeWritePool; + FIO_rust_freeCResourcesCallback_f freeReadPool; + FIO_rust_freeCResourcesCallback_f freeCctx; +} FIO_rust_freeCResourcesState; +typedef char FIO_rust_free_c_resources_state_layout[ + (offsetof(FIO_rust_freeCResourcesState, callbackContext) == 0 + && offsetof(FIO_rust_freeCResourcesState, freeDict) == sizeof(void*) + && offsetof(FIO_rust_freeCResourcesState, freeWritePool) + == 2 * sizeof(void*) + && offsetof(FIO_rust_freeCResourcesState, freeReadPool) + == 3 * sizeof(void*) + && offsetof(FIO_rust_freeCResourcesState, freeCctx) + == 4 * sizeof(void*) + && sizeof(FIO_rust_freeCResourcesState) == 5 * sizeof(void*)) + ? 1 : -1]; +void FIO_rust_freeCResources(const FIO_rust_freeCResourcesState* state); + +static void FIO_rust_freeCResources_dict(void* context) +{ + cRess_t* const ress = (cRess_t*)context; + FIO_freeDict(&(ress->dict)); +} + +static void FIO_rust_freeCResources_writePool(void* context) +{ + cRess_t* const ress = (cRess_t*)context; + AIO_WritePool_free(ress->writeCtx); +} + +static void FIO_rust_freeCResources_readPool(void* context) +{ + cRess_t* const ress = (cRess_t*)context; + AIO_ReadPool_free(ress->readCtx); +} + +static void FIO_rust_freeCResources_cctx(void* context) +{ + cRess_t* const ress = (cRess_t*)context; + ZSTD_freeCStream(ress->cctx); /* never fails */ +} + static void FIO_freeCResources(cRess_t* const ress) { - FIO_freeDict(&(ress->dict)); - AIO_WritePool_free(ress->writeCtx); - AIO_ReadPool_free(ress->readCtx); - ZSTD_freeCStream(ress->cctx); /* never fails */ + FIO_rust_freeCResourcesState const state = { + ress, + FIO_rust_freeCResources_dict, + FIO_rust_freeCResources_writePool, + FIO_rust_freeCResources_readPool, + FIO_rust_freeCResources_cctx + }; + FIO_rust_freeCResources(&state); } diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index 7e24e5805..f3da5b522 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -12,7 +12,7 @@ use std::ffi::{c_void, CStr}; use std::io::{self, Write}; -use std::mem::size_of; +use std::mem::{offset_of, size_of}; use std::os::raw::{c_char, c_int, c_uint}; use std::ptr; @@ -103,6 +103,50 @@ pub struct FIO_listFileInfoProjection_t { nbFiles: u32, } +type FIO_freeCResourcesCallbackFn = unsafe extern "C" fn(*mut c_void); + +/// C-owned compression-resource teardown projection. +/// +/// Rust owns the stable cleanup order while C keeps `cRess_t`, dictionary, +/// asynchronous-pool, and stream layouts private behind callbacks. +#[repr(C)] +pub struct FIO_rust_freeCResourcesState { + callback_context: *mut c_void, + free_dict: FIO_freeCResourcesCallbackFn, + free_write_pool: FIO_freeCResourcesCallbackFn, + free_read_pool: FIO_freeCResourcesCallbackFn, + free_cctx: FIO_freeCResourcesCallbackFn, +} + +const _: () = { + assert!(offset_of!(FIO_rust_freeCResourcesState, callback_context) == 0); + assert!(offset_of!(FIO_rust_freeCResourcesState, free_dict) == size_of::()); + assert!( + offset_of!(FIO_rust_freeCResourcesState, free_write_pool) == 2 * size_of::() + ); + assert!( + offset_of!(FIO_rust_freeCResourcesState, free_read_pool) == 3 * size_of::() + ); + assert!(offset_of!(FIO_rust_freeCResourcesState, free_cctx) == 4 * size_of::()); + assert!(size_of::() == 5 * size_of::()); +}; + +#[no_mangle] +pub unsafe extern "C" fn FIO_rust_freeCResources( + state: *const FIO_rust_freeCResourcesState, +) { + if state.is_null() { + return; + } + let state = unsafe { &*state }; + unsafe { + (state.free_dict)(state.callback_context); + (state.free_write_pool)(state.callback_context); + (state.free_read_pool)(state.callback_context); + (state.free_cctx)(state.callback_context); + } +} + pub type FIO_listMultipleFilesIsStdinFn = unsafe extern "C" fn(*mut c_void, *const c_char) -> c_int; pub type FIO_listMultipleFilesDisplayFn = unsafe extern "C" fn(*mut c_void); pub type FIO_listMultipleFilesListFileFn = unsafe extern "C" fn( @@ -1869,6 +1913,50 @@ mod tests { use std::fs; use std::mem::{align_of, offset_of, size_of}; + #[derive(Default)] + struct FreeCResourcesTestState { + events: Vec<&'static str>, + } + + unsafe extern "C" fn free_c_resources_dict_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("dict"); + } + + unsafe extern "C" fn free_c_resources_write_pool_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("write"); + } + + unsafe extern "C" fn free_c_resources_read_pool_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("read"); + } + + unsafe extern "C" fn free_c_resources_cctx_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("cctx"); + } + + #[test] + fn free_c_resources_preserves_teardown_order_and_null_policy() { + let mut context = FreeCResourcesTestState::default(); + let state = FIO_rust_freeCResourcesState { + callback_context: &mut context as *mut FreeCResourcesTestState as *mut c_void, + free_dict: free_c_resources_dict_test, + free_write_pool: free_c_resources_write_pool_test, + free_read_pool: free_c_resources_read_pool_test, + free_cctx: free_c_resources_cctx_test, + }; + + unsafe { + FIO_rust_freeCResources(ptr::null()); + FIO_rust_freeCResources(&state); + } + + assert_eq!(context.events, ["dict", "write", "read", "cctx"]); + } + fn temporary_file_path(name: &str) -> std::path::PathBuf { std::env::temp_dir().join(format!("zstd-fileio-prefs-{}-{name}", std::process::id())) }