diff --git a/programs/fileio.c b/programs/fileio.c index a72f88511..33ba8f14e 100644 --- a/programs/fileio.c +++ b/programs/fileio.c @@ -3648,6 +3648,51 @@ typedef struct { ReadPoolCtx_t *readCtx; } dRess_t; +typedef void (*FIO_rust_freeDResourcesCallback_f)(void* context); +typedef struct { + void* callbackContext; + FIO_rust_freeDResourcesCallback_f freeDict; + FIO_rust_freeDResourcesCallback_f freeDctx; + FIO_rust_freeDResourcesCallback_f freeWritePool; + FIO_rust_freeDResourcesCallback_f freeReadPool; +} FIO_rust_freeDResourcesState; +typedef char FIO_rust_free_d_resources_state_layout[ + (offsetof(FIO_rust_freeDResourcesState, callbackContext) == 0 + && offsetof(FIO_rust_freeDResourcesState, freeDict) == sizeof(void*) + && offsetof(FIO_rust_freeDResourcesState, freeDctx) + == 2 * sizeof(void*) + && offsetof(FIO_rust_freeDResourcesState, freeWritePool) + == 3 * sizeof(void*) + && offsetof(FIO_rust_freeDResourcesState, freeReadPool) + == 4 * sizeof(void*) + && sizeof(FIO_rust_freeDResourcesState) == 5 * sizeof(void*)) + ? 1 : -1]; +void FIO_rust_freeDResources(const FIO_rust_freeDResourcesState* state); + +static void FIO_rust_freeDResources_dict(void* context) +{ + dRess_t* const ress = (dRess_t*)context; + FIO_freeDict(&(ress->dict)); +} + +static void FIO_rust_freeDResources_dctx(void* context) +{ + dRess_t* const ress = (dRess_t*)context; + CHECK( ZSTD_freeDStream(ress->dctx) ); +} + +static void FIO_rust_freeDResources_writePool(void* context) +{ + dRess_t* const ress = (dRess_t*)context; + AIO_WritePool_free(ress->writeCtx); +} + +static void FIO_rust_freeDResources_readPool(void* context) +{ + dRess_t* const ress = (dRess_t*)context; + AIO_ReadPool_free(ress->readCtx); +} + static dRess_t FIO_createDResources(FIO_prefs_t* const prefs, const char* dictFileName) { int useMMap = prefs->mmapDict == ZSTD_ps_enable; @@ -3693,10 +3738,14 @@ static dRess_t FIO_createDResources(FIO_prefs_t* const prefs, const char* dictFi static void FIO_freeDResources(dRess_t ress) { - FIO_freeDict(&(ress.dict)); - CHECK( ZSTD_freeDStream(ress.dctx) ); - AIO_WritePool_free(ress.writeCtx); - AIO_ReadPool_free(ress.readCtx); + FIO_rust_freeDResourcesState const state = { + &ress, + FIO_rust_freeDResources_dict, + FIO_rust_freeDResources_dctx, + FIO_rust_freeDResources_writePool, + FIO_rust_freeDResources_readPool + }; + FIO_rust_freeDResources(&state); } /* FIO_passThrough() : just copy input into output, for compatibility with gzip -df mode diff --git a/rust/src/fileio_prefs.rs b/rust/src/fileio_prefs.rs index 2bb01f918..9515d55a3 100644 --- a/rust/src/fileio_prefs.rs +++ b/rust/src/fileio_prefs.rs @@ -173,6 +173,56 @@ pub unsafe extern "C" fn FIO_rust_freeCResources( } } +type FIO_freeDResourcesCallbackFn = unsafe extern "C" fn(*mut c_void); + +/// C-owned decompression-resource teardown projection. +/// +/// Rust owns the stable cleanup order while C keeps `dRess_t`, dictionary, +/// asynchronous-pool, and stream layouts private behind callbacks. +#[repr(C)] +pub struct FIO_rust_freeDResourcesState { + callback_context: *mut c_void, + free_dict: Option, + free_dctx: Option, + free_write_pool: Option, + free_read_pool: Option, +} + +const _: () = { + assert!(offset_of!(FIO_rust_freeDResourcesState, callback_context) == 0); + assert!(offset_of!(FIO_rust_freeDResourcesState, free_dict) == size_of::()); + assert!(offset_of!(FIO_rust_freeDResourcesState, free_dctx) == 2 * size_of::()); + assert!(offset_of!(FIO_rust_freeDResourcesState, free_write_pool) == 3 * size_of::()); + assert!(offset_of!(FIO_rust_freeDResourcesState, free_read_pool) == 4 * size_of::()); + assert!(size_of::>() == size_of::()); + assert!(size_of::() == 5 * size_of::()); +}; + +#[no_mangle] +pub unsafe extern "C" fn FIO_rust_freeDResources(state: *const FIO_rust_freeDResourcesState) { + let Some(state) = (unsafe { state.as_ref() }) else { + return; + }; + if state.callback_context.is_null() { + return; + } + let (Some(free_dict), Some(free_dctx), Some(free_write_pool), Some(free_read_pool)) = ( + state.free_dict, + state.free_dctx, + state.free_write_pool, + state.free_read_pool, + ) else { + return; + }; + + unsafe { + free_dict(state.callback_context); + free_dctx(state.callback_context); + free_write_pool(state.callback_context); + free_read_pool(state.callback_context); + } +} + pub type FIO_createCResourcesSetParameterFn = unsafe extern "C" fn(*mut c_void, c_int, c_int) -> usize; pub type FIO_createCResourcesIsErrorFn = unsafe extern "C" fn(usize) -> c_int; @@ -2169,6 +2219,67 @@ mod tests { assert_eq!(context.events, ["dict", "write", "read", "cctx"]); } + #[derive(Default)] + struct FreeDResourcesTestState { + events: Vec<&'static str>, + } + + unsafe extern "C" fn free_d_resources_dict_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("dict"); + } + + unsafe extern "C" fn free_d_resources_dctx_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("dctx"); + } + + unsafe extern "C" fn free_d_resources_write_pool_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("write"); + } + + unsafe extern "C" fn free_d_resources_read_pool_test(context: *mut c_void) { + let state = unsafe { &mut *context.cast::() }; + state.events.push("read"); + } + + fn free_d_resources_test_state( + context: &mut FreeDResourcesTestState, + ) -> FIO_rust_freeDResourcesState { + FIO_rust_freeDResourcesState { + callback_context: context as *mut FreeDResourcesTestState as *mut c_void, + free_dict: Some(free_d_resources_dict_test), + free_dctx: Some(free_d_resources_dctx_test), + free_write_pool: Some(free_d_resources_write_pool_test), + free_read_pool: Some(free_d_resources_read_pool_test), + } + } + + #[test] + fn free_d_resources_preserves_teardown_order() { + let mut context = FreeDResourcesTestState::default(); + let state = free_d_resources_test_state(&mut context); + + unsafe { FIO_rust_freeDResources(&state) }; + + assert_eq!(context.events, ["dict", "dctx", "write", "read"]); + } + + #[test] + fn free_d_resources_rejects_null_or_incomplete_state_without_callbacks() { + let mut context = FreeDResourcesTestState::default(); + let mut incomplete = free_d_resources_test_state(&mut context); + incomplete.free_dctx = None; + + unsafe { + FIO_rust_freeDResources(ptr::null()); + FIO_rust_freeDResources(&incomplete); + } + + assert!(context.events.is_empty()); + } + #[derive(Default)] struct CreateCResourcesTestState { events: Vec<(c_int, c_int)>,