feat(programs): move pass-through copying into Rust

Keep decompression resource ownership and file orchestration in C while the
Rust AIO module handles the pass-through byte-copy leaf through a two-pool ABI.
The Rust implementation derives the block limit from the actual read and write
job buffers, preventing a write-job overrun when their configured sizes differ.
It preserves the existing enqueue/reacquire, consume/refill, EOF, release, and
sparse-write ordering, with pool-backed coverage for synchronous and threaded
operation.

Test Plan:
- `cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression --lib fileio_asyncio` -- passed, 9 tests.
- Required clippy/fmt sequence against `rust/Cargo.toml` with compression -- passed before and after nightly fmt.
- `cargo test --manifest-path rust/Cargo.toml --no-default-features --features compression --lib` -- passed, 342 tests.
- `make -C programs -j2 zstd` -- passed.
- Manual 200 KiB and empty-input `programs/zstd -dc --pass-through` comparisons -- passed.
This commit is contained in:
2026-07-18 14:53:44 +02:00
parent 0dfe757d95
commit 8ade3da099
2 changed files with 114 additions and 18 deletions
+2 -18
View File
@@ -343,6 +343,7 @@ int FIO_rust_setDictBufferMalloc(const char* fileName,
void** buffer,
size_t* loadedSize);
int FIO_rust_removeFile(const char* path);
int FIO_rust_passThrough(ReadPoolCtx_t* readCtx, WritePoolCtx_t* writeCtx);
#ifdef ZSTD_LZ4COMPRESS
int FIO_rust_LZ4_GetBlockSize_FromBlockId(int id);
#endif
@@ -2123,24 +2124,7 @@ static void FIO_freeDResources(dRess_t ress)
* @return : 0 (no error) */
static int FIO_passThrough(dRess_t *ress)
{
size_t const blockSize = MIN(MIN(64 KB, ZSTD_DStreamInSize()), ZSTD_DStreamOutSize());
IOJob_t *writeJob = AIO_WritePool_acquireJob(ress->writeCtx);
AIO_ReadPool_fillBuffer(ress->readCtx, blockSize);
while(ress->readCtx->srcBufferLoaded) {
size_t writeSize;
writeSize = MIN(blockSize, ress->readCtx->srcBufferLoaded);
assert(writeSize <= writeJob->bufferSize);
memcpy(writeJob->buffer, ress->readCtx->srcBuffer, writeSize);
writeJob->usedBufferSize = writeSize;
AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
AIO_ReadPool_consumeBytes(ress->readCtx, writeSize);
AIO_ReadPool_fillBuffer(ress->readCtx, blockSize);
}
assert(ress->readCtx->reachedEof);
AIO_WritePool_releaseIoJob(writeJob);
AIO_WritePool_sparseWriteEnd(ress->writeCtx);
return 0;
return FIO_rust_passThrough(ress->readCtx, ress->writeCtx);
}
/* FIO_zstdErrorHelp() :
+112
View File
@@ -29,6 +29,7 @@ const MAX_IO_JOBS: usize = 10;
const IO_QUEUE_SIZE: usize = MAX_IO_JOBS - 2;
const SPARSE_SEGMENT_SIZE: usize = 32 * 1024;
const SPARSE_SKIP_CHUNK: u64 = 1 << 30;
const PASS_THROUGH_MAX_BLOCK_SIZE: usize = 64 * 1024;
/// C's `FIO_prefs_t` from `programs/fileio_types.h`.
///
@@ -1350,6 +1351,48 @@ pub unsafe extern "C" fn AIO_ReadPool_closeFile(ctx: *mut ReadPoolCtx_t) -> c_in
unsafe { libc::fclose(file) }
}
#[no_mangle]
pub unsafe extern "C" fn FIO_rust_passThrough(
read_ctx: *mut ReadPoolCtx_t,
write_ctx: *mut WritePoolCtx_t,
) -> c_int {
assert!(!read_ctx.is_null());
assert!(!write_ctx.is_null());
let read_context = read_ctx.cast::<u8>();
let write_context = write_ctx.cast::<u8>();
let read_inner = unsafe { base_inner(read_context) };
let write_inner = unsafe { base_inner(write_context) };
let block_size = PASS_THROUGH_MAX_BLOCK_SIZE
.min(unsafe { read_at::<usize>(read_context, read_inner.layout.job_buffer_size) })
.min(unsafe { read_at::<usize>(write_context, write_inner.layout.job_buffer_size) });
let mut write_job = unsafe { AIO_WritePool_acquireJob(write_ctx) };
unsafe { AIO_ReadPool_fillBuffer(read_ctx, block_size) };
while unsafe { read_at::<usize>(read_context, read_inner.layout.read_src_buffer_loaded) } != 0 {
let loaded =
unsafe { read_at::<usize>(read_context, read_inner.layout.read_src_buffer_loaded) };
let write_size = block_size.min(loaded);
assert!(write_size <= unsafe { (*write_job).bufferSize });
let source =
unsafe { read_at::<*const u8>(read_context, read_inner.layout.read_src_buffer) };
unsafe {
ptr::copy_nonoverlapping(source, (*write_job).buffer.cast::<u8>(), write_size);
(*write_job).usedBufferSize = write_size;
AIO_WritePool_enqueueAndReacquireWriteJob(&mut write_job);
AIO_ReadPool_consumeBytes(read_ctx, write_size);
AIO_ReadPool_fillBuffer(read_ctx, block_size);
}
}
assert!(unsafe { read_at::<c_int>(read_context, read_inner.layout.read_reached_eof) } != 0);
unsafe {
AIO_WritePool_releaseIoJob(write_job);
AIO_WritePool_sparseWriteEnd(write_ctx);
}
0
}
impl PoolInner {
unsafe fn all_jobs_available(&self) -> bool {
let state = self.jobs.lock().unwrap_or_else(|error| error.into_inner());
@@ -1739,4 +1782,73 @@ mod tests {
);
unsafe { AIO_ReadPool_free(ctx) };
}
#[cfg(unix)]
fn run_pass_through(
input: &[u8],
read_buffer_size: usize,
write_buffer_size: usize,
async_io: c_int,
) -> Vec<u8> {
let input_file = unsafe { libc::tmpfile() };
assert!(!input_file.is_null());
if !input.is_empty() {
assert_eq!(
unsafe { libc::fwrite(input.as_ptr().cast(), 1, input.len(), input_file) },
input.len()
);
}
assert_eq!(unsafe { libc::fflush(input_file) }, 0);
assert_eq!(unsafe { libc::fseek(input_file, 0, libc::SEEK_SET) }, 0);
let output_file = unsafe { libc::tmpfile() };
assert!(!output_file.is_null());
let mut prefs = test_prefs(async_io);
prefs.testMode = 0;
prefs.sparseFileSupport = 0;
let read_ctx = unsafe { AIO_ReadPool_create(&prefs, read_buffer_size) };
let write_ctx = unsafe { AIO_WritePool_create(&prefs, write_buffer_size) };
unsafe {
AIO_ReadPool_setFile(read_ctx, input_file);
AIO_WritePool_setFile(write_ctx, output_file);
}
assert_eq!(unsafe { FIO_rust_passThrough(read_ctx, write_ctx) }, 0);
assert_eq!(unsafe { libc::fseek(output_file, 0, libc::SEEK_END) }, 0);
let output_size = unsafe { libc::ftell(output_file) };
assert!(output_size >= 0);
let output_size = usize::try_from(output_size).unwrap();
assert_eq!(output_size, input.len());
assert_eq!(unsafe { libc::fseek(output_file, 0, libc::SEEK_SET) }, 0);
let mut output = vec![0_u8; output_size];
if output_size != 0 {
assert_eq!(
unsafe { libc::fread(output.as_mut_ptr().cast(), 1, output_size, output_file) },
output_size
);
}
unsafe {
AIO_WritePool_free(write_ctx);
AIO_ReadPool_free(read_ctx);
}
output
}
#[cfg(unix)]
#[test]
fn pass_through_copies_across_bounded_job_buffers() {
let input = b"pass-through data spans several blocks";
for async_io in [0, 1] {
assert_eq!(run_pass_through(input, 5, 3, async_io).as_slice(), input);
}
}
#[cfg(unix)]
#[test]
fn pass_through_handles_empty_input() {
for async_io in [0, 1] {
assert!(run_pass_through(&[], 5, 3, async_io).is_empty());
}
}
}