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
This commit is contained in:
2026-07-20 04:38:09 +02:00
parent 66e7799f04
commit dc83c98f06
2 changed files with 142 additions and 5 deletions
+53 -4
View File
@@ -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);
}
+89 -1
View File
@@ -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::<usize>());
assert!(
offset_of!(FIO_rust_freeCResourcesState, free_write_pool) == 2 * size_of::<usize>()
);
assert!(
offset_of!(FIO_rust_freeCResourcesState, free_read_pool) == 3 * size_of::<usize>()
);
assert!(offset_of!(FIO_rust_freeCResourcesState, free_cctx) == 4 * size_of::<usize>());
assert!(size_of::<FIO_rust_freeCResourcesState>() == 5 * size_of::<usize>());
};
#[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::<FreeCResourcesTestState>() };
state.events.push("dict");
}
unsafe extern "C" fn free_c_resources_write_pool_test(context: *mut c_void) {
let state = unsafe { &mut *context.cast::<FreeCResourcesTestState>() };
state.events.push("write");
}
unsafe extern "C" fn free_c_resources_read_pool_test(context: *mut c_void) {
let state = unsafe { &mut *context.cast::<FreeCResourcesTestState>() };
state.events.push("read");
}
unsafe extern "C" fn free_c_resources_cctx_test(context: *mut c_void) {
let state = unsafe { &mut *context.cast::<FreeCResourcesTestState>() };
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()))
}