From 2b2f9df5de78fb536618708f68cd5cb88a30abe9 Mon Sep 17 00:00:00 2001 From: ddidderr Date: Mon, 20 Jul 2026 06:44:03 +0200 Subject: [PATCH] refactor(mt): move context construction policy to Rust Move MT context validation, worker clamping, resource-construction order, and partial-failure cleanup policy into Rust. Keep the private context layout, allocator-owned resources, pools, jobs table, and synchronization primitives in C behind callbacks. Test Plan: - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo +nightly fmt --manifest-path rust/Cargo.toml -- --check - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/Cargo.toml --all-targets (782 passed) - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo clippy --manifest-path rust/cli/Cargo.toml --all-targets -- -D warnings - ulimit -v 41943040; CARGO_BUILD_JOBS=1 cargo test --manifest-path rust/cli/Cargo.toml --all-targets (179 passed) - ulimit -v 41943040; make -j1 - ulimit -v 41943040; make -j1 -C tests test (all tests completed successfully) --- lib/compress/zstdmt_compress.c | 194 ++++++++++++++---- rust/src/zstdmt_compress.rs | 365 +++++++++++++++++++++++++++++++++ 2 files changed, 520 insertions(+), 39 deletions(-) diff --git a/lib/compress/zstdmt_compress.c b/lib/compress/zstdmt_compress.c index 09c352b58..3a8833f32 100644 --- a/lib/compress/zstdmt_compress.c +++ b/lib/compress/zstdmt_compress.c @@ -642,6 +642,46 @@ typedef char ZSTDMT_rust_free_cctx_state_layout[ && offsetof(ZSTDMT_RustFreeCCtxState, freeMTctx) == 12 * sizeof(void*) && sizeof(ZSTDMT_RustFreeCCtxState) == 13 * sizeof(void*)) ? 1 : -1]; size_t ZSTDMT_rust_freeCCtx(const ZSTDMT_RustFreeCCtxState* state); + +typedef struct { + void* callbackContext; + unsigned requestedNbWorkers; + unsigned maxNbWorkers; + size_t contextSize; + ZSTD_customMem customMem; +} ZSTDMT_RustCreateCCtxProjection; +typedef char ZSTDMT_rust_create_cctx_projection_layout[ + (offsetof(ZSTDMT_RustCreateCCtxProjection, callbackContext) == 0 + && offsetof(ZSTDMT_RustCreateCCtxProjection, requestedNbWorkers) + == sizeof(void*) + && offsetof(ZSTDMT_RustCreateCCtxProjection, maxNbWorkers) + == sizeof(void*) + sizeof(unsigned) + && offsetof(ZSTDMT_RustCreateCCtxProjection, contextSize) + == (sizeof(void*) == 8 ? 16 : 12) + && offsetof(ZSTDMT_RustCreateCCtxProjection, customMem) + == (sizeof(void*) == 8 ? 24 : 16) + && sizeof(ZSTDMT_RustCreateCCtxProjection) + == (sizeof(void*) == 8 ? 48 : 28)) ? 1 : -1]; +typedef void* (*ZSTDMT_createCCtxAllocateFn)(void* opaque, size_t size); +typedef size_t (*ZSTDMT_createCCtxSetWorkersFn)(void* opaque, unsigned nbWorkers); +typedef void (*ZSTDMT_createCCtxSetInitialStateFn)(void* opaque); +typedef void* (*ZSTDMT_createCCtxFactoryFn)(void* opaque, unsigned nbWorkers); +typedef void* (*ZSTDMT_createCCtxJobsFn)(void* opaque, unsigned* nbJobs); +typedef void* (*ZSTDMT_createCCtxResourceFn)(void* opaque, unsigned nbWorkers); +typedef int (*ZSTDMT_createCCtxSerialInitFn)(void* opaque); +typedef void (*ZSTDMT_createCCtxFreeFn)(void* opaque); +void* ZSTDMT_rust_createCCtx( + const ZSTDMT_RustCreateCCtxProjection* projection, + ZSTDMT_createCCtxAllocateFn allocate, + ZSTDMT_createCCtxSetWorkersFn setWorkers, + ZSTDMT_createCCtxSetInitialStateFn setInitialState, + ZSTDMT_createCCtxFactoryFn createFactory, + ZSTDMT_createCCtxJobsFn createJobs, + ZSTDMT_createCCtxResourceFn createBufferPool, + ZSTDMT_createCCtxResourceFn createCCtxPool, + ZSTDMT_createCCtxResourceFn createSeqPool, + ZSTDMT_createCCtxSerialInitFn initSerial, + ZSTDMT_createCCtxFreeFn freeContext); size_t ZSTDMT_rust_initCStream( const ZSTDMT_RustInitCStreamProjection* projection, void* opaque, ZSTDMT_initResizeFn resize, ZSTDMT_initDrainFn drain, @@ -1700,8 +1740,6 @@ typedef struct { */ } RoundBuff_t; -static const RoundBuff_t kNullRoundBuff = {NULL, 0, 0}; - #define RSYNC_LENGTH 32 /* Don't create chunks smaller than the zstd block size. * This stops us from regressing compression ratio too much, @@ -1831,46 +1869,124 @@ static size_t ZSTDMT_CCtxParam_setNbWorkers(ZSTD_CCtx_params* params, unsigned n return ZSTD_CCtxParams_setParameter(params, ZSTD_c_nbWorkers, (int)nbWorkers); } +typedef struct { + ZSTDMT_CCtx* mtctx; + ZSTD_customMem cMem; + ZSTD_threadPool* pool; +} ZSTDMT_rust_createCCtx_context; + +static void* ZSTDMT_rust_createCCtx_allocate(void* opaque, size_t size) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + context->mtctx = (ZSTDMT_CCtx*)ZSTD_customCalloc(size, context->cMem); + return context->mtctx; +} + +static size_t ZSTDMT_rust_createCCtx_setWorkers(void* opaque, unsigned nbWorkers) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + return ZSTDMT_CCtxParam_setNbWorkers(&context->mtctx->params, nbWorkers); +} + +static void ZSTDMT_rust_createCCtx_setInitialState(void* opaque) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + context->mtctx->cMem = context->cMem; + context->mtctx->allJobsCompleted = 1; +} + +static void* ZSTDMT_rust_createCCtx_factory(void* opaque, unsigned nbWorkers) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + if (context->pool != NULL) { + context->mtctx->factory = context->pool; + context->mtctx->providedFactory = 1; + } else { + context->mtctx->factory = POOL_create_advanced(nbWorkers, 0, context->cMem); + context->mtctx->providedFactory = 0; + } + return context->mtctx->factory; +} + +static void* ZSTDMT_rust_createCCtx_jobs(void* opaque, unsigned* nbJobs) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + context->mtctx->jobs = ZSTDMT_createJobsTable((U32*)nbJobs, context->cMem); + if (context->mtctx->jobs != NULL) + context->mtctx->jobIDMask = *nbJobs - 1; + return context->mtctx->jobs; +} + +static void* ZSTDMT_rust_createCCtx_bufferPool(void* opaque, unsigned nbWorkers) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + context->mtctx->bufPool = ZSTDMT_createBufferPool( + BUF_POOL_MAX_NB_BUFFERS(nbWorkers), context->cMem); + return context->mtctx->bufPool; +} + +static void* ZSTDMT_rust_createCCtx_cctxPool(void* opaque, unsigned nbWorkers) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + context->mtctx->cctxPool = ZSTDMT_createCCtxPool(nbWorkers, context->cMem); + return context->mtctx->cctxPool; +} + +static void* ZSTDMT_rust_createCCtx_seqPool(void* opaque, unsigned nbWorkers) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + context->mtctx->seqPool = ZSTDMT_createSeqPool(nbWorkers, context->cMem); + return context->mtctx->seqPool; +} + +static int ZSTDMT_rust_createCCtx_initSerial(void* opaque) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + return ZSTDMT_serialState_init(&context->mtctx->serial); +} + +static void ZSTDMT_rust_createCCtx_free(void* opaque) +{ + ZSTDMT_rust_createCCtx_context* const context = + (ZSTDMT_rust_createCCtx_context*)opaque; + if (context->mtctx != NULL) { + ZSTDMT_freeCCtx(context->mtctx); + context->mtctx = NULL; + } +} + MEM_STATIC ZSTDMT_CCtx* ZSTDMT_createCCtx_advanced_internal(unsigned nbWorkers, ZSTD_customMem cMem, ZSTD_threadPool* pool) { - ZSTDMT_CCtx* mtctx; - U32 nbJobs = nbWorkers + 2; - int initError; + ZSTDMT_rust_createCCtx_context context = { NULL, cMem, pool }; + ZSTDMT_RustCreateCCtxProjection const projection = { + &context, + nbWorkers, + ZSTDMT_NBWORKERS_MAX, + sizeof(ZSTDMT_CCtx), + cMem + }; DEBUGLOG(3, "ZSTDMT_createCCtx_advanced (nbWorkers = %u)", nbWorkers); - - if (nbWorkers < 1) return NULL; - nbWorkers = MIN(nbWorkers , ZSTDMT_NBWORKERS_MAX); - if ((cMem.customAlloc!=NULL) ^ (cMem.customFree!=NULL)) - /* invalid custom allocator */ - return NULL; - - mtctx = (ZSTDMT_CCtx*) ZSTD_customCalloc(sizeof(ZSTDMT_CCtx), cMem); - if (!mtctx) return NULL; - ZSTDMT_CCtxParam_setNbWorkers(&mtctx->params, nbWorkers); - mtctx->cMem = cMem; - mtctx->allJobsCompleted = 1; - if (pool != NULL) { - mtctx->factory = pool; - mtctx->providedFactory = 1; - } - else { - mtctx->factory = POOL_create_advanced(nbWorkers, 0, cMem); - mtctx->providedFactory = 0; - } - mtctx->jobs = ZSTDMT_createJobsTable(&nbJobs, cMem); - assert(nbJobs > 0); assert((nbJobs & (nbJobs - 1)) == 0); /* ensure nbJobs is a power of 2 */ - mtctx->jobIDMask = nbJobs - 1; - mtctx->bufPool = ZSTDMT_createBufferPool(BUF_POOL_MAX_NB_BUFFERS(nbWorkers), cMem); - mtctx->cctxPool = ZSTDMT_createCCtxPool(nbWorkers, cMem); - mtctx->seqPool = ZSTDMT_createSeqPool(nbWorkers, cMem); - initError = ZSTDMT_serialState_init(&mtctx->serial); - mtctx->roundBuff = kNullRoundBuff; - if (!mtctx->factory | !mtctx->jobs | !mtctx->bufPool | !mtctx->cctxPool | !mtctx->seqPool | initError) { - ZSTDMT_freeCCtx(mtctx); - return NULL; - } - DEBUGLOG(3, "mt_cctx created, for %u threads", nbWorkers); - return mtctx; + return (ZSTDMT_CCtx*)ZSTDMT_rust_createCCtx( + &projection, + ZSTDMT_rust_createCCtx_allocate, + ZSTDMT_rust_createCCtx_setWorkers, + ZSTDMT_rust_createCCtx_setInitialState, + ZSTDMT_rust_createCCtx_factory, + ZSTDMT_rust_createCCtx_jobs, + ZSTDMT_rust_createCCtx_bufferPool, + ZSTDMT_rust_createCCtx_cctxPool, + ZSTDMT_rust_createCCtx_seqPool, + ZSTDMT_rust_createCCtx_initSerial, + ZSTDMT_rust_createCCtx_free); } ZSTDMT_CCtx* ZSTDMT_createCCtx_advanced(unsigned nbWorkers, ZSTD_customMem cMem, ZSTD_threadPool* pool) diff --git a/rust/src/zstdmt_compress.rs b/rust/src/zstdmt_compress.rs index 2ca133704..173ed5591 100644 --- a/rust/src/zstdmt_compress.rs +++ b/rust/src/zstdmt_compress.rs @@ -468,6 +468,149 @@ const _: () = { assert!(size_of::() == size_of::<[usize; 13]>()); }; +pub type ZSTDMT_createCCtxAllocateFn = unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void; +pub type ZSTDMT_createCCtxSetWorkersFn = unsafe extern "C" fn(*mut c_void, c_uint) -> usize; +pub type ZSTDMT_createCCtxSetInitialStateFn = unsafe extern "C" fn(*mut c_void); +pub type ZSTDMT_createCCtxFactoryFn = unsafe extern "C" fn(*mut c_void, c_uint) -> *mut c_void; +pub type ZSTDMT_createCCtxJobsFn = unsafe extern "C" fn(*mut c_void, *mut c_uint) -> *mut c_void; +pub type ZSTDMT_createCCtxResourceFn = unsafe extern "C" fn(*mut c_void, c_uint) -> *mut c_void; +pub type ZSTDMT_createCCtxSerialInitFn = unsafe extern "C" fn(*mut c_void) -> c_int; +pub type ZSTDMT_createCCtxFreeFn = unsafe extern "C" fn(*mut c_void); + +/// Scalar inputs and callbacks for MT context construction. Rust owns the +/// validation, worker clamp, and resource-construction order; C retains the +/// private context layout, allocator calls, pools, and synchronization. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct ZSTDMT_RustCreateCCtxProjection { + callback_context: *mut c_void, + requested_nb_workers: c_uint, + max_nb_workers: c_uint, + context_size: usize, + custom_mem: ZstdCustomMem, +} + +const _: () = { + assert!(offset_of!(ZSTDMT_RustCreateCCtxProjection, callback_context) == 0); + assert!( + offset_of!(ZSTDMT_RustCreateCCtxProjection, requested_nb_workers) == size_of::() + ); + assert!( + offset_of!(ZSTDMT_RustCreateCCtxProjection, max_nb_workers) + == size_of::() + size_of::() + ); + assert!( + offset_of!(ZSTDMT_RustCreateCCtxProjection, context_size) + == if size_of::() == 8 { 16 } else { 12 } + ); + assert!( + offset_of!(ZSTDMT_RustCreateCCtxProjection, custom_mem) + == if size_of::() == 8 { 24 } else { 16 } + ); + assert!( + size_of::() + == if size_of::() == 8 { 48 } else { 28 } + ); +}; + +fn custom_mem_is_valid(custom_mem: ZstdCustomMem) -> bool { + custom_mem.customAlloc.is_some() == custom_mem.customFree.is_some() +} + +/// Construct an MT context through the original C resource callbacks. +/// Callbacks are all invoked through the allocation and cleanup sequence even +/// after an intermediate resource returns NULL, matching the C constructor's +/// cleanup contract for partially initialized contexts. +#[no_mangle] +pub unsafe extern "C" fn ZSTDMT_rust_createCCtx( + projection: *const ZSTDMT_RustCreateCCtxProjection, + allocate: Option, + set_workers: Option, + set_initial_state: Option, + create_factory: Option, + create_jobs: Option, + create_buffer_pool: Option, + create_cctx_pool: Option, + create_seq_pool: Option, + init_serial: Option, + free_context: Option, +) -> *mut c_void { + let Some(projection) = (unsafe { projection.as_ref() }).copied() else { + return ptr::null_mut(); + }; + let ( + Some(allocate), + Some(set_workers), + Some(set_initial_state), + Some(create_factory), + Some(create_jobs), + Some(create_buffer_pool), + Some(create_cctx_pool), + Some(create_seq_pool), + Some(init_serial), + Some(free_context), + ) = ( + allocate, + set_workers, + set_initial_state, + create_factory, + create_jobs, + create_buffer_pool, + create_cctx_pool, + create_seq_pool, + init_serial, + free_context, + ) + else { + return ptr::null_mut(); + }; + + if projection.callback_context.is_null() + || projection.requested_nb_workers == 0 + || projection.max_nb_workers == 0 + || projection.context_size == 0 + || !custom_mem_is_valid(projection.custom_mem) + { + return ptr::null_mut(); + } + + let nb_workers = projection + .requested_nb_workers + .min(projection.max_nb_workers); + let mut nb_jobs = projection.requested_nb_workers.wrapping_add(2); + let context = unsafe { allocate(projection.callback_context, projection.context_size) }; + if context.is_null() { + return ptr::null_mut(); + } + + // The C constructor ignores this setter's result for a valid worker + // count; preserve that exact policy before publishing the custom memory. + unsafe { + let _ = set_workers(projection.callback_context, nb_workers); + set_initial_state(projection.callback_context); + } + + let factory = unsafe { create_factory(projection.callback_context, nb_workers) }; + let jobs = unsafe { create_jobs(projection.callback_context, &mut nb_jobs) }; + let buffer_pool = unsafe { create_buffer_pool(projection.callback_context, nb_workers) }; + let cctx_pool = unsafe { create_cctx_pool(projection.callback_context, nb_workers) }; + let seq_pool = unsafe { create_seq_pool(projection.callback_context, nb_workers) }; + let serial_error = unsafe { init_serial(projection.callback_context) }; + + if factory.is_null() + || jobs.is_null() + || buffer_pool.is_null() + || cctx_pool.is_null() + || seq_pool.is_null() + || serial_error != 0 + { + unsafe { free_context(projection.callback_context) }; + return ptr::null_mut(); + } + + context +} + 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); @@ -3932,6 +4075,109 @@ mod tests { events: Vec<&'static str>, } + #[derive(Default)] + struct CreateCCtxTestContext { + events: Vec<&'static str>, + workers: c_uint, + jobs: c_uint, + factory_ok: bool, + resource_calls: usize, + } + + fn record_create_cctx_event(context: *mut c_void, event: &'static str) { + unsafe { + (*context.cast::()) + .events + .push(event); + } + } + + unsafe extern "C" fn create_cctx_test_allocate( + context: *mut c_void, + _size: usize, + ) -> *mut c_void { + record_create_cctx_event(context, "allocate"); + ptr::dangling_mut::() + } + + unsafe extern "C" fn create_cctx_test_set_workers( + context: *mut c_void, + workers: c_uint, + ) -> usize { + record_create_cctx_event(context, "workers"); + unsafe { (*context.cast::()).workers = workers }; + 0 + } + + unsafe extern "C" fn create_cctx_test_initial_state(context: *mut c_void) { + record_create_cctx_event(context, "initial-state"); + } + + unsafe extern "C" fn create_cctx_test_factory( + context: *mut c_void, + _workers: c_uint, + ) -> *mut c_void { + record_create_cctx_event(context, "factory"); + let context = unsafe { &*context.cast::() }; + if context.factory_ok { + ptr::dangling_mut::() + } else { + ptr::null_mut() + } + } + + unsafe extern "C" fn create_cctx_test_jobs( + context: *mut c_void, + jobs: *mut c_uint, + ) -> *mut c_void { + record_create_cctx_event(context, "jobs"); + unsafe { + (*context.cast::()).jobs = 8; + *jobs = 8; + } + ptr::dangling_mut::() + } + + unsafe extern "C" fn create_cctx_test_resource( + context: *mut c_void, + _workers: c_uint, + ) -> *mut c_void { + let event = unsafe { + let state = &mut *context.cast::(); + let event = match state.resource_calls { + 0 => "buffer-pool", + 1 => "cctx-pool", + _ => "seq-pool", + }; + state.resource_calls += 1; + event + }; + record_create_cctx_event(context, event); + ptr::dangling_mut::() + } + + unsafe extern "C" fn create_cctx_test_serial_init(context: *mut c_void) -> c_int { + record_create_cctx_event(context, "serial"); + 0 + } + + unsafe extern "C" fn create_cctx_test_free(context: *mut c_void) { + record_create_cctx_event(context, "free"); + } + + fn create_cctx_test_projection( + context: &mut CreateCCtxTestContext, + custom_mem: ZstdCustomMem, + ) -> ZSTDMT_RustCreateCCtxProjection { + ZSTDMT_RustCreateCCtxProjection { + callback_context: context as *mut CreateCCtxTestContext as *mut c_void, + requested_nb_workers: 99, + max_nb_workers: 4, + context_size: 128, + custom_mem, + } + } + fn record_free_cctx_event(context: *mut c_void, event: &'static str) { unsafe { (*context.cast::()).events.push(event); @@ -4005,6 +4251,125 @@ mod tests { assert_eq!(unsafe { ZSTDMT_rust_freeCCtx(ptr::null()) }, 0); } + #[test] + fn create_cctx_preserves_resource_order_and_clamps_workers() { + let mut context = CreateCCtxTestContext { + factory_ok: true, + ..CreateCCtxTestContext::default() + }; + let projection = create_cctx_test_projection(&mut context, DEFAULT_MEM); + + let result = unsafe { + ZSTDMT_rust_createCCtx( + &projection, + Some(create_cctx_test_allocate), + Some(create_cctx_test_set_workers), + Some(create_cctx_test_initial_state), + Some(create_cctx_test_factory), + Some(create_cctx_test_jobs), + Some(create_cctx_test_resource), + Some(create_cctx_test_resource), + Some(create_cctx_test_resource), + Some(create_cctx_test_serial_init), + Some(create_cctx_test_free), + ) + }; + + assert_eq!(result, ptr::dangling_mut::()); + assert_eq!(context.workers, 4); + assert_eq!(context.jobs, 8); + assert_eq!( + context.events, + vec![ + "allocate", + "workers", + "initial-state", + "factory", + "jobs", + "buffer-pool", + "cctx-pool", + "seq-pool", + "serial", + ] + ); + } + + #[test] + fn create_cctx_runs_remaining_callbacks_before_failure_cleanup() { + let mut context = CreateCCtxTestContext::default(); + let projection = create_cctx_test_projection(&mut context, DEFAULT_MEM); + + let result = unsafe { + ZSTDMT_rust_createCCtx( + &projection, + Some(create_cctx_test_allocate), + Some(create_cctx_test_set_workers), + Some(create_cctx_test_initial_state), + Some(create_cctx_test_factory), + Some(create_cctx_test_jobs), + Some(create_cctx_test_resource), + Some(create_cctx_test_resource), + Some(create_cctx_test_resource), + Some(create_cctx_test_serial_init), + Some(create_cctx_test_free), + ) + }; + + assert!(result.is_null()); + assert_eq!( + context.events, + vec![ + "allocate", + "workers", + "initial-state", + "factory", + "jobs", + "buffer-pool", + "cctx-pool", + "seq-pool", + "serial", + "free", + ] + ); + } + + unsafe extern "C" fn create_cctx_test_alloc(_opaque: *mut c_void, _size: usize) -> *mut c_void { + ptr::null_mut() + } + + #[test] + fn create_cctx_rejects_mismatched_custom_allocator() { + let mut context = CreateCCtxTestContext { + factory_ok: true, + ..CreateCCtxTestContext::default() + }; + let invalid_mem = ZstdCustomMem { + customAlloc: Some(create_cctx_test_alloc), + customFree: None, + opaque: ptr::null_mut(), + }; + let projection = create_cctx_test_projection(&mut context, invalid_mem); + + let result = unsafe { + ZSTDMT_rust_createCCtx( + &projection, + Some(create_cctx_test_allocate), + Some(create_cctx_test_set_workers), + Some(create_cctx_test_initial_state), + Some(create_cctx_test_factory), + Some(create_cctx_test_jobs), + Some(create_cctx_test_resource), + Some(create_cctx_test_resource), + Some(create_cctx_test_resource), + Some(create_cctx_test_serial_init), + Some(create_cctx_test_free), + ) + }; + + assert!(result.is_null()); + assert!(context.events.is_empty()); + } + #[test] fn free_cctx_preserves_callback_order_and_ownership_conditions() { let mut context = FreeCCtxTestContext::default();