feat(cli): move zstd compression loop into Rust
The CLI's zstd-format compression path previously kept the asynchronous read, compressStream2, output-job, flush, accounting, adaptive-policy, and progress loop together in fileio.c. That made the high-level stream orchestration another large C-owned surface even though the Rust fileio module already owned the neighboring format loops. Move the format-independent zstd stream loop and read/output accounting into FIO_rust_compressZstdFrame. The C adapter now projects read-pool, write-pool, codec, and policy operations through narrow callbacks. C retains the private ZSTD_CCtx interaction, adaptive-level policy, memory diagnostics, progress formatting, and CLI error mapping, so no private C layout crosses into Rust. Focused seam tests cover input/output ordering, final flush behavior, codec error propagation, and incomplete known-size input handling. Test Plan: - `cargo test --manifest-path rust/Cargo.toml --all-targets -- --test-threads=1` -- 513 passed under the 40 GiB virtual-memory cap. - `cargo test --manifest-path rust/cli/Cargo.toml --all-targets -- --test-threads=1` -- 169 passed under the cap. - `cargo clippy` lib/benches/tests for `rust` and `rust/cli`, with `-D warnings`, and nightly formatting -- passed. - `make -B -C lib -j1 lib` and `make -B -C programs -j1 zstd` -- passed. - Native CLI, full zstd, fuzzer, zstream, and decode-corpus targets -- passed serially under the cap. GPG signing was attempted but unavailable because no pinentry process was available; this repository's preceding commits are unsigned, so this commit uses the explicit unsigned fallback.
This commit is contained in:
+375
-221
@@ -1125,6 +1125,55 @@ typedef struct {
|
||||
FIO_rust_compress_status_display_fn display_status;
|
||||
} FIO_rust_compress_callbacks_t;
|
||||
|
||||
enum {
|
||||
FIO_RUST_ZSTD_OK = 0,
|
||||
FIO_RUST_ZSTD_COMPRESS_ERROR = 1,
|
||||
FIO_RUST_ZSTD_INCOMPLETE_INPUT = 2,
|
||||
FIO_RUST_ZSTD_INVALID_PROJECTION = 3
|
||||
};
|
||||
|
||||
typedef size_t (*FIO_rust_zstd_read_fill_fn)(
|
||||
void* opaque, size_t requested,
|
||||
const unsigned char** buffer, size_t* loaded);
|
||||
typedef void (*FIO_rust_zstd_read_consume_fn)(void* opaque, size_t consumed);
|
||||
typedef void (*FIO_rust_zstd_write_acquire_fn)(
|
||||
void* opaque, void** job, unsigned char** buffer, size_t* bufferSize);
|
||||
typedef void (*FIO_rust_zstd_write_enqueue_fn)(
|
||||
void* opaque, void** job, size_t usedBufferSize,
|
||||
unsigned char** buffer, size_t* bufferSize);
|
||||
typedef void (*FIO_rust_zstd_write_release_fn)(void* opaque, void* job);
|
||||
typedef void (*FIO_rust_zstd_sparse_write_end_fn)(void* opaque);
|
||||
typedef int (*FIO_rust_zstd_compress_stream_fn)(
|
||||
void* opaque, const char* srcFileName, int directive,
|
||||
const unsigned char* input, size_t inputSize, size_t inputPos,
|
||||
unsigned char* output, size_t outputSize,
|
||||
size_t* inputPosAfter, size_t* outputProduced,
|
||||
size_t* toFlushNow, size_t* zstdResult);
|
||||
typedef void (*FIO_rust_zstd_iteration_fn)(
|
||||
void* opaque, const char* srcFileName, int* compressionLevel,
|
||||
size_t oldInputPos, size_t newInputPos, size_t toFlushNow);
|
||||
|
||||
typedef struct {
|
||||
void* readOpaque;
|
||||
void* writeOpaque;
|
||||
void* codecOpaque;
|
||||
void* policyOpaque;
|
||||
size_t readBufferSize;
|
||||
FIO_rust_zstd_read_fill_fn readFill;
|
||||
FIO_rust_zstd_read_consume_fn readConsume;
|
||||
FIO_rust_zstd_write_acquire_fn writeAcquire;
|
||||
FIO_rust_zstd_write_enqueue_fn writeEnqueue;
|
||||
FIO_rust_zstd_write_release_fn writeRelease;
|
||||
FIO_rust_zstd_sparse_write_end_fn sparseWriteEnd;
|
||||
FIO_rust_zstd_compress_stream_fn compressStream;
|
||||
FIO_rust_zstd_iteration_fn iteration;
|
||||
} FIO_rust_zstd_compress_projection_t;
|
||||
|
||||
int FIO_rust_compressZstdFrame(
|
||||
const FIO_rust_zstd_compress_projection_t* projection,
|
||||
const char* srcFileName, U64 srcFileSize, int compressionLevel,
|
||||
U64* readsize, U64* compressedSize, size_t* zstdResult);
|
||||
|
||||
enum {
|
||||
FIO_RUST_GZIP_OK = 0,
|
||||
FIO_RUST_GZIP_INIT_ERROR = 1,
|
||||
@@ -1913,241 +1962,266 @@ FIO_compressLz4Frame(cRess_t* ress,
|
||||
}
|
||||
#endif
|
||||
|
||||
static unsigned long long
|
||||
FIO_compressZstdFrame(FIO_ctx_t* const fCtx,
|
||||
FIO_prefs_t* const prefs,
|
||||
const cRess_t* ressPtr,
|
||||
const char* srcFileName, U64 fileSize,
|
||||
int compressionLevel, U64* readsize)
|
||||
typedef enum {
|
||||
FIO_rust_zstd_noChange,
|
||||
FIO_rust_zstd_slower,
|
||||
FIO_rust_zstd_faster
|
||||
} FIO_rust_zstd_speed_change_e;
|
||||
|
||||
typedef struct {
|
||||
FIO_ctx_t* fCtx;
|
||||
FIO_prefs_t* prefs;
|
||||
ZSTD_CCtx* cctx;
|
||||
ZSTD_frameProgression previousZfpUpdate;
|
||||
ZSTD_frameProgression previousZfpCorrection;
|
||||
FIO_rust_zstd_speed_change_e speedChange;
|
||||
unsigned flushWaiting;
|
||||
unsigned inputPresented;
|
||||
unsigned inputBlocked;
|
||||
unsigned lastJobID;
|
||||
UTIL_time_t lastAdaptTime;
|
||||
U64 srcFileSize;
|
||||
UTIL_HumanReadableSize_t fileHrs;
|
||||
} FIO_rust_zstd_projection_context_t;
|
||||
|
||||
static size_t FIO_rust_zstd_readFill(void* opaque, size_t requested,
|
||||
const unsigned char** buffer, size_t* loaded)
|
||||
{
|
||||
cRess_t const ress = *ressPtr;
|
||||
IOJob_t* writeJob = AIO_WritePool_acquireJob(ressPtr->writeCtx);
|
||||
ReadPoolCtx_t* const readCtx = (ReadPoolCtx_t*)opaque;
|
||||
size_t const added = AIO_ReadPool_fillBuffer(readCtx, requested);
|
||||
*buffer = readCtx->srcBuffer;
|
||||
*loaded = readCtx->srcBufferLoaded;
|
||||
DISPLAYLEVEL(6, "fread %u bytes from source \n", (unsigned)added);
|
||||
return added;
|
||||
}
|
||||
|
||||
U64 compressedfilesize = 0;
|
||||
ZSTD_EndDirective directive = ZSTD_e_continue;
|
||||
U64 pledgedSrcSize = ZSTD_CONTENTSIZE_UNKNOWN;
|
||||
static void FIO_rust_zstd_readConsume(void* opaque, size_t consumed)
|
||||
{
|
||||
AIO_ReadPool_consumeBytes((ReadPoolCtx_t*)opaque, consumed);
|
||||
}
|
||||
|
||||
/* stats */
|
||||
ZSTD_frameProgression previous_zfp_update = { 0, 0, 0, 0, 0, 0 };
|
||||
ZSTD_frameProgression previous_zfp_correction = { 0, 0, 0, 0, 0, 0 };
|
||||
typedef enum { noChange, slower, faster } speedChange_e;
|
||||
speedChange_e speedChange = noChange;
|
||||
unsigned flushWaiting = 0;
|
||||
unsigned inputPresented = 0;
|
||||
unsigned inputBlocked = 0;
|
||||
unsigned lastJobID = 0;
|
||||
UTIL_time_t lastAdaptTime = UTIL_getTime();
|
||||
U64 const adaptEveryMicro = REFRESH_RATE;
|
||||
static void FIO_rust_zstd_writeAcquire(void* opaque, void** job,
|
||||
unsigned char** buffer, size_t* bufferSize)
|
||||
{
|
||||
IOJob_t* const writeJob = AIO_WritePool_acquireJob((WritePoolCtx_t*)opaque);
|
||||
*job = writeJob;
|
||||
*buffer = (unsigned char*)writeJob->buffer;
|
||||
*bufferSize = writeJob->bufferSize;
|
||||
}
|
||||
|
||||
UTIL_HumanReadableSize_t const file_hrs = UTIL_makeHumanReadableSize(fileSize);
|
||||
static void FIO_rust_zstd_writeEnqueue(void* opaque, void** job,
|
||||
size_t usedBufferSize,
|
||||
unsigned char** buffer, size_t* bufferSize)
|
||||
{
|
||||
IOJob_t* writeJob = (IOJob_t*)*job;
|
||||
writeJob->usedBufferSize = usedBufferSize;
|
||||
AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
|
||||
*job = writeJob;
|
||||
*buffer = (unsigned char*)writeJob->buffer;
|
||||
*bufferSize = writeJob->bufferSize;
|
||||
(void)opaque;
|
||||
}
|
||||
|
||||
DISPLAYLEVEL(6, "compression using zstd format \n");
|
||||
static void FIO_rust_zstd_writeRelease(void* opaque, void* job)
|
||||
{
|
||||
AIO_WritePool_releaseIoJob((IOJob_t*)job);
|
||||
(void)opaque;
|
||||
}
|
||||
|
||||
/* init */
|
||||
if (fileSize != UTIL_FILESIZE_UNKNOWN) {
|
||||
pledgedSrcSize = fileSize;
|
||||
CHECK(ZSTD_CCtx_setPledgedSrcSize(ress.cctx, fileSize));
|
||||
} else if (prefs->streamSrcSize > 0) {
|
||||
/* unknown source size; use the declared stream size */
|
||||
pledgedSrcSize = prefs->streamSrcSize;
|
||||
CHECK( ZSTD_CCtx_setPledgedSrcSize(ress.cctx, prefs->streamSrcSize) );
|
||||
static void FIO_rust_zstd_sparseWriteEnd(void* opaque)
|
||||
{
|
||||
AIO_WritePool_sparseWriteEnd((WritePoolCtx_t*)opaque);
|
||||
}
|
||||
|
||||
static int FIO_rust_zstd_compressStream(
|
||||
void* opaque, const char* srcFileName, int directive,
|
||||
const unsigned char* input, size_t inputSize, size_t inputPos,
|
||||
unsigned char* output, size_t outputSize,
|
||||
size_t* inputPosAfter, size_t* outputProduced,
|
||||
size_t* toFlushNow, size_t* zstdResult)
|
||||
{
|
||||
FIO_rust_zstd_projection_context_t* const context =
|
||||
(FIO_rust_zstd_projection_context_t*)opaque;
|
||||
ZSTD_inBuffer inBuff = setInBuffer(input, inputSize, inputPos);
|
||||
ZSTD_outBuffer outBuff = setOutBuffer(output, outputSize, 0);
|
||||
size_t const toFlush = ZSTD_toFlushNow(context->cctx);
|
||||
size_t const result = ZSTD_compressStream2(
|
||||
context->cctx, &outBuff, &inBuff, (ZSTD_EndDirective)directive);
|
||||
|
||||
*inputPosAfter = inBuff.pos;
|
||||
*outputProduced = outBuff.pos;
|
||||
*toFlushNow = toFlush;
|
||||
*zstdResult = result;
|
||||
if (!ZSTD_isError(result)) {
|
||||
DISPLAYLEVEL(6, "ZSTD_compress_generic(end:%u) => input pos(%u)<=(%u)size ; output generated %u bytes \n",
|
||||
(unsigned)directive, (unsigned)inBuff.pos,
|
||||
(unsigned)inBuff.size, (unsigned)outBuff.pos);
|
||||
}
|
||||
(void)srcFileName;
|
||||
return ZSTD_isError(result);
|
||||
}
|
||||
|
||||
{ int windowLog;
|
||||
UTIL_HumanReadableSize_t windowSize;
|
||||
CHECK(ZSTD_CCtx_getParameter(ress.cctx, ZSTD_c_windowLog, &windowLog));
|
||||
if (windowLog == 0) {
|
||||
if (prefs->ldmFlag) {
|
||||
/* If long mode is set without a window size libzstd will set this size internally */
|
||||
windowLog = ZSTD_WINDOWLOG_LIMIT_DEFAULT;
|
||||
} else {
|
||||
const ZSTD_compressionParameters cParams = ZSTD_getCParams(compressionLevel, fileSize, 0);
|
||||
windowLog = (int)cParams.windowLog;
|
||||
static void FIO_rust_zstd_iteration(void* opaque, const char* srcFileName,
|
||||
int* compressionLevel,
|
||||
size_t oldInputPos, size_t newInputPos,
|
||||
size_t toFlushNow)
|
||||
{
|
||||
FIO_rust_zstd_projection_context_t* const context =
|
||||
(FIO_rust_zstd_projection_context_t*)opaque;
|
||||
FIO_prefs_t* const prefs = context->prefs;
|
||||
FIO_ctx_t* const fCtx = context->fCtx;
|
||||
|
||||
context->inputPresented++;
|
||||
if (oldInputPos == newInputPos) context->inputBlocked++;
|
||||
if (!toFlushNow) context->flushWaiting = 1;
|
||||
|
||||
/* Adaptive mode remains in C so its policy and private preference/context
|
||||
* access stay identical while Rust owns the surrounding stream loop. */
|
||||
if (prefs->adaptiveMode &&
|
||||
UTIL_clockSpanMicro(context->lastAdaptTime) > REFRESH_RATE) {
|
||||
ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(context->cctx);
|
||||
|
||||
context->lastAdaptTime = UTIL_getTime();
|
||||
|
||||
/* check output speed */
|
||||
if (zfp.currentJobID > 1) { /* only possible if nbWorkers >= 1 */
|
||||
unsigned long long const newlyProduced =
|
||||
zfp.produced - context->previousZfpUpdate.produced;
|
||||
unsigned long long const newlyFlushed =
|
||||
zfp.flushed - context->previousZfpUpdate.flushed;
|
||||
assert(zfp.produced >= context->previousZfpUpdate.produced);
|
||||
assert(prefs->nbWorkers >= 1);
|
||||
|
||||
/* test if compression is blocked
|
||||
* either because output is slow and all buffers are full
|
||||
* or because input is slow and no job can start while waiting for at least one buffer to be filled.
|
||||
* note : exclude starting part, since currentJobID > 1 */
|
||||
if ((zfp.consumed == context->previousZfpUpdate.consumed)
|
||||
&& (zfp.nbActiveWorkers == 0)) {
|
||||
DISPLAYLEVEL(6, "all buffers full : compression stopped => slow down \n")
|
||||
context->speedChange = FIO_rust_zstd_slower;
|
||||
}
|
||||
|
||||
context->previousZfpUpdate = zfp;
|
||||
|
||||
if ((newlyProduced > (newlyFlushed * 9 / 8))
|
||||
&& (context->flushWaiting == 0)) {
|
||||
DISPLAYLEVEL(6, "compression faster than flush (%llu > %llu), and flushed was never slowed down by lack of production => slow down \n",
|
||||
newlyProduced, newlyFlushed);
|
||||
context->speedChange = FIO_rust_zstd_slower;
|
||||
}
|
||||
context->flushWaiting = 0;
|
||||
}
|
||||
windowSize = UTIL_makeHumanReadableSize(MAX(1ULL, MIN(1ULL << windowLog, pledgedSrcSize)));
|
||||
DISPLAYLEVEL(4, "Decompression will require %.*f%s of memory\n", windowSize.precision, windowSize.value, windowSize.suffix);
|
||||
}
|
||||
|
||||
/* Main compression loop */
|
||||
do {
|
||||
size_t stillToFlush;
|
||||
/* Fill input Buffer */
|
||||
size_t const inSize = AIO_ReadPool_fillBuffer(ress.readCtx, ZSTD_CStreamInSize());
|
||||
ZSTD_inBuffer inBuff = setInBuffer( ress.readCtx->srcBuffer, ress.readCtx->srcBufferLoaded, 0 );
|
||||
DISPLAYLEVEL(6, "fread %u bytes from source \n", (unsigned)inSize);
|
||||
*readsize += inSize;
|
||||
/* course correct only if there is at least one new job completed */
|
||||
if (zfp.currentJobID > context->lastJobID) {
|
||||
DISPLAYLEVEL(6, "compression level adaptation check \n")
|
||||
|
||||
if ((ress.readCtx->srcBufferLoaded == 0) || (*readsize == fileSize))
|
||||
directive = ZSTD_e_end;
|
||||
|
||||
stillToFlush = 1;
|
||||
while ((inBuff.pos != inBuff.size) /* input buffer must be entirely ingested */
|
||||
|| (directive == ZSTD_e_end && stillToFlush != 0) ) {
|
||||
|
||||
size_t const oldIPos = inBuff.pos;
|
||||
ZSTD_outBuffer outBuff = setOutBuffer( writeJob->buffer, writeJob->bufferSize, 0 );
|
||||
size_t const toFlushNow = ZSTD_toFlushNow(ress.cctx);
|
||||
CHECK_V(stillToFlush, ZSTD_compressStream2(ress.cctx, &outBuff, &inBuff, directive));
|
||||
AIO_ReadPool_consumeBytes(ress.readCtx, inBuff.pos - oldIPos);
|
||||
|
||||
/* count stats */
|
||||
inputPresented++;
|
||||
if (oldIPos == inBuff.pos) inputBlocked++; /* input buffer is full and can't take any more : input speed is faster than consumption rate */
|
||||
if (!toFlushNow) flushWaiting = 1;
|
||||
|
||||
/* Write compressed stream */
|
||||
DISPLAYLEVEL(6, "ZSTD_compress_generic(end:%u) => input pos(%u)<=(%u)size ; output generated %u bytes \n",
|
||||
(unsigned)directive, (unsigned)inBuff.pos, (unsigned)inBuff.size, (unsigned)outBuff.pos);
|
||||
if (outBuff.pos) {
|
||||
writeJob->usedBufferSize = outBuff.pos;
|
||||
AIO_WritePool_enqueueAndReacquireWriteJob(&writeJob);
|
||||
compressedfilesize += outBuff.pos;
|
||||
/* check input speed */
|
||||
if (zfp.currentJobID > (unsigned)(prefs->nbWorkers+1)) {
|
||||
if (context->inputBlocked <= 0) {
|
||||
DISPLAYLEVEL(6, "input is never blocked => input is slower than ingestion \n");
|
||||
context->speedChange = FIO_rust_zstd_slower;
|
||||
} else if (context->speedChange == FIO_rust_zstd_noChange) {
|
||||
unsigned long long const newlyIngested =
|
||||
zfp.ingested - context->previousZfpCorrection.ingested;
|
||||
unsigned long long const newlyConsumed =
|
||||
zfp.consumed - context->previousZfpCorrection.consumed;
|
||||
unsigned long long const newlyProduced =
|
||||
zfp.produced - context->previousZfpCorrection.produced;
|
||||
unsigned long long const newlyFlushed =
|
||||
zfp.flushed - context->previousZfpCorrection.flushed;
|
||||
context->previousZfpCorrection = zfp;
|
||||
assert(context->inputPresented > 0);
|
||||
DISPLAYLEVEL(6, "input blocked %u/%u(%.2f) - ingested:%u vs %u:consumed - flushed:%u vs %u:produced \n",
|
||||
context->inputBlocked, context->inputPresented,
|
||||
(double)context->inputBlocked/context->inputPresented*100,
|
||||
(unsigned)newlyIngested, (unsigned)newlyConsumed,
|
||||
(unsigned)newlyFlushed, (unsigned)newlyProduced);
|
||||
if ((context->inputBlocked > context->inputPresented / 8)
|
||||
&& (newlyFlushed * 33 / 32 > newlyProduced)
|
||||
&& (newlyIngested * 33 / 32 > newlyConsumed)) {
|
||||
DISPLAYLEVEL(6, "recommend faster as in(%llu) >= (%llu)comp(%llu) <= out(%llu) \n",
|
||||
newlyIngested, newlyConsumed,
|
||||
newlyProduced, newlyFlushed);
|
||||
context->speedChange = FIO_rust_zstd_faster;
|
||||
}
|
||||
}
|
||||
context->inputBlocked = 0;
|
||||
context->inputPresented = 0;
|
||||
}
|
||||
|
||||
/* adaptive mode : statistics measurement and speed correction */
|
||||
if (prefs->adaptiveMode && UTIL_clockSpanMicro(lastAdaptTime) > adaptEveryMicro) {
|
||||
ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(ress.cctx);
|
||||
|
||||
lastAdaptTime = UTIL_getTime();
|
||||
|
||||
/* check output speed */
|
||||
if (zfp.currentJobID > 1) { /* only possible if nbWorkers >= 1 */
|
||||
|
||||
unsigned long long newlyProduced = zfp.produced - previous_zfp_update.produced;
|
||||
unsigned long long newlyFlushed = zfp.flushed - previous_zfp_update.flushed;
|
||||
assert(zfp.produced >= previous_zfp_update.produced);
|
||||
assert(prefs->nbWorkers >= 1);
|
||||
|
||||
/* test if compression is blocked
|
||||
* either because output is slow and all buffers are full
|
||||
* or because input is slow and no job can start while waiting for at least one buffer to be filled.
|
||||
* note : exclude starting part, since currentJobID > 1 */
|
||||
if ( (zfp.consumed == previous_zfp_update.consumed) /* no data compressed : no data available, or no more buffer to compress to, OR compression is really slow (compression of a single block is slower than update rate)*/
|
||||
&& (zfp.nbActiveWorkers == 0) /* confirmed : no compression ongoing */
|
||||
) {
|
||||
DISPLAYLEVEL(6, "all buffers full : compression stopped => slow down \n")
|
||||
speedChange = slower;
|
||||
}
|
||||
|
||||
previous_zfp_update = zfp;
|
||||
|
||||
if ( (newlyProduced > (newlyFlushed * 9 / 8)) /* compression produces more data than output can flush (though production can be spiky, due to work unit : (N==4)*block sizes) */
|
||||
&& (flushWaiting == 0) /* flush speed was never slowed by lack of production, so it's operating at max capacity */
|
||||
) {
|
||||
DISPLAYLEVEL(6, "compression faster than flush (%llu > %llu), and flushed was never slowed down by lack of production => slow down \n", newlyProduced, newlyFlushed);
|
||||
speedChange = slower;
|
||||
}
|
||||
flushWaiting = 0;
|
||||
}
|
||||
|
||||
/* course correct only if there is at least one new job completed */
|
||||
if (zfp.currentJobID > lastJobID) {
|
||||
DISPLAYLEVEL(6, "compression level adaptation check \n")
|
||||
|
||||
/* check input speed */
|
||||
if (zfp.currentJobID > (unsigned)(prefs->nbWorkers+1)) { /* warm up period, to fill all workers */
|
||||
if (inputBlocked <= 0) {
|
||||
DISPLAYLEVEL(6, "input is never blocked => input is slower than ingestion \n");
|
||||
speedChange = slower;
|
||||
} else if (speedChange == noChange) {
|
||||
unsigned long long newlyIngested = zfp.ingested - previous_zfp_correction.ingested;
|
||||
unsigned long long newlyConsumed = zfp.consumed - previous_zfp_correction.consumed;
|
||||
unsigned long long newlyProduced = zfp.produced - previous_zfp_correction.produced;
|
||||
unsigned long long newlyFlushed = zfp.flushed - previous_zfp_correction.flushed;
|
||||
previous_zfp_correction = zfp;
|
||||
assert(inputPresented > 0);
|
||||
DISPLAYLEVEL(6, "input blocked %u/%u(%.2f) - ingested:%u vs %u:consumed - flushed:%u vs %u:produced \n",
|
||||
inputBlocked, inputPresented, (double)inputBlocked/inputPresented*100,
|
||||
(unsigned)newlyIngested, (unsigned)newlyConsumed,
|
||||
(unsigned)newlyFlushed, (unsigned)newlyProduced);
|
||||
if ( (inputBlocked > inputPresented / 8) /* input is waiting often, because input buffers is full : compression or output too slow */
|
||||
&& (newlyFlushed * 33 / 32 > newlyProduced) /* flush everything that is produced */
|
||||
&& (newlyIngested * 33 / 32 > newlyConsumed) /* input speed as fast or faster than compression speed */
|
||||
) {
|
||||
DISPLAYLEVEL(6, "recommend faster as in(%llu) >= (%llu)comp(%llu) <= out(%llu) \n",
|
||||
newlyIngested, newlyConsumed, newlyProduced, newlyFlushed);
|
||||
speedChange = faster;
|
||||
}
|
||||
}
|
||||
inputBlocked = 0;
|
||||
inputPresented = 0;
|
||||
}
|
||||
|
||||
if (speedChange == slower) {
|
||||
DISPLAYLEVEL(6, "slower speed , higher compression \n")
|
||||
compressionLevel ++;
|
||||
if (compressionLevel > ZSTD_maxCLevel()) compressionLevel = ZSTD_maxCLevel();
|
||||
if (compressionLevel > prefs->maxAdaptLevel) compressionLevel = prefs->maxAdaptLevel;
|
||||
compressionLevel += (compressionLevel == 0); /* skip 0 */
|
||||
ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, compressionLevel);
|
||||
}
|
||||
if (speedChange == faster) {
|
||||
DISPLAYLEVEL(6, "faster speed , lighter compression \n")
|
||||
compressionLevel --;
|
||||
if (compressionLevel < prefs->minAdaptLevel) compressionLevel = prefs->minAdaptLevel;
|
||||
compressionLevel -= (compressionLevel == 0); /* skip 0 */
|
||||
ZSTD_CCtx_setParameter(ress.cctx, ZSTD_c_compressionLevel, compressionLevel);
|
||||
}
|
||||
speedChange = noChange;
|
||||
|
||||
lastJobID = zfp.currentJobID;
|
||||
} /* if (zfp.currentJobID > lastJobID) */
|
||||
} /* if (prefs->adaptiveMode && UTIL_clockSpanMicro(lastAdaptTime) > adaptEveryMicro) */
|
||||
|
||||
/* display notification */
|
||||
if (SHOULD_DISPLAY_PROGRESS() && READY_FOR_UPDATE()) {
|
||||
ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(ress.cctx);
|
||||
double const cShare = (double)zfp.produced / (double)(zfp.consumed + !zfp.consumed/*avoid div0*/) * 100;
|
||||
UTIL_HumanReadableSize_t const buffered_hrs = UTIL_makeHumanReadableSize(zfp.ingested - zfp.consumed);
|
||||
UTIL_HumanReadableSize_t const consumed_hrs = UTIL_makeHumanReadableSize(zfp.consumed);
|
||||
UTIL_HumanReadableSize_t const produced_hrs = UTIL_makeHumanReadableSize(zfp.produced);
|
||||
|
||||
DELAY_NEXT_UPDATE();
|
||||
|
||||
/* display progress notifications */
|
||||
DISPLAY_PROGRESS("\r%79s\r", ""); /* Clear out the current displayed line */
|
||||
if (g_display_prefs.displayLevel >= 3) {
|
||||
/* Verbose progress update */
|
||||
DISPLAY_PROGRESS(
|
||||
"(L%i) Buffered:%5.*f%s - Consumed:%5.*f%s - Compressed:%5.*f%s => %.2f%% ",
|
||||
compressionLevel,
|
||||
buffered_hrs.precision, buffered_hrs.value, buffered_hrs.suffix,
|
||||
consumed_hrs.precision, consumed_hrs.value, consumed_hrs.suffix,
|
||||
produced_hrs.precision, produced_hrs.value, produced_hrs.suffix,
|
||||
cShare );
|
||||
} else {
|
||||
/* Require level 2 or forcibly displayed progress counter for summarized updates */
|
||||
if (fCtx->nbFilesTotal > 1) {
|
||||
size_t srcFileNameSize = strlen(srcFileName);
|
||||
/* Ensure that the string we print is roughly the same size each time */
|
||||
if (srcFileNameSize > 18) {
|
||||
const char* truncatedSrcFileName = srcFileName + srcFileNameSize - 15;
|
||||
DISPLAY_PROGRESS("Compress: %u/%u files. Current: ...%s ",
|
||||
fCtx->currFileIdx+1, fCtx->nbFilesTotal, truncatedSrcFileName);
|
||||
} else {
|
||||
DISPLAY_PROGRESS("Compress: %u/%u files. Current: %*s ",
|
||||
fCtx->currFileIdx+1, fCtx->nbFilesTotal, (int)(18-srcFileNameSize), srcFileName);
|
||||
}
|
||||
}
|
||||
DISPLAY_PROGRESS("Read:%6.*f%4s ", consumed_hrs.precision, consumed_hrs.value, consumed_hrs.suffix);
|
||||
if (fileSize != UTIL_FILESIZE_UNKNOWN)
|
||||
DISPLAY_PROGRESS("/%6.*f%4s", file_hrs.precision, file_hrs.value, file_hrs.suffix);
|
||||
DISPLAY_PROGRESS(" ==> %2.f%%", cShare);
|
||||
}
|
||||
} /* if (SHOULD_DISPLAY_PROGRESS() && READY_FOR_UPDATE()) */
|
||||
} /* while ((inBuff.pos != inBuff.size) */
|
||||
} while (directive != ZSTD_e_end);
|
||||
|
||||
if (fileSize != UTIL_FILESIZE_UNKNOWN && *readsize != fileSize) {
|
||||
EXM_THROW(27, "Read error : Incomplete read : %llu / %llu B",
|
||||
(unsigned long long)*readsize, (unsigned long long)fileSize);
|
||||
if (context->speedChange == FIO_rust_zstd_slower) {
|
||||
DISPLAYLEVEL(6, "slower speed , higher compression \n")
|
||||
(*compressionLevel)++;
|
||||
if (*compressionLevel > ZSTD_maxCLevel())
|
||||
*compressionLevel = ZSTD_maxCLevel();
|
||||
if (*compressionLevel > prefs->maxAdaptLevel)
|
||||
*compressionLevel = prefs->maxAdaptLevel;
|
||||
*compressionLevel += (*compressionLevel == 0);
|
||||
ZSTD_CCtx_setParameter(context->cctx,
|
||||
ZSTD_c_compressionLevel,
|
||||
*compressionLevel);
|
||||
}
|
||||
if (context->speedChange == FIO_rust_zstd_faster) {
|
||||
DISPLAYLEVEL(6, "faster speed , lighter compression \n")
|
||||
(*compressionLevel)--;
|
||||
if (*compressionLevel < prefs->minAdaptLevel)
|
||||
*compressionLevel = prefs->minAdaptLevel;
|
||||
*compressionLevel -= (*compressionLevel == 0);
|
||||
ZSTD_CCtx_setParameter(context->cctx,
|
||||
ZSTD_c_compressionLevel,
|
||||
*compressionLevel);
|
||||
}
|
||||
context->speedChange = FIO_rust_zstd_noChange;
|
||||
context->lastJobID = zfp.currentJobID;
|
||||
}
|
||||
}
|
||||
|
||||
AIO_WritePool_releaseIoJob(writeJob);
|
||||
AIO_WritePool_sparseWriteEnd(ressPtr->writeCtx);
|
||||
/* Keep progress formatting and the frame-progression query in C. */
|
||||
if (SHOULD_DISPLAY_PROGRESS() && READY_FOR_UPDATE()) {
|
||||
ZSTD_frameProgression const zfp = ZSTD_getFrameProgression(context->cctx);
|
||||
double const cShare = (double)zfp.produced /
|
||||
(double)(zfp.consumed + !zfp.consumed) * 100;
|
||||
UTIL_HumanReadableSize_t const buffered_hrs =
|
||||
UTIL_makeHumanReadableSize(zfp.ingested - zfp.consumed);
|
||||
UTIL_HumanReadableSize_t const consumed_hrs =
|
||||
UTIL_makeHumanReadableSize(zfp.consumed);
|
||||
UTIL_HumanReadableSize_t const produced_hrs =
|
||||
UTIL_makeHumanReadableSize(zfp.produced);
|
||||
|
||||
return compressedfilesize;
|
||||
DELAY_NEXT_UPDATE();
|
||||
DISPLAY_PROGRESS("\r%79s\r", "");
|
||||
if (g_display_prefs.displayLevel >= 3) {
|
||||
DISPLAY_PROGRESS(
|
||||
"(L%i) Buffered:%5.*f%s - Consumed:%5.*f%s - Compressed:%5.*f%s => %.2f%% ",
|
||||
*compressionLevel,
|
||||
buffered_hrs.precision, buffered_hrs.value, buffered_hrs.suffix,
|
||||
consumed_hrs.precision, consumed_hrs.value, consumed_hrs.suffix,
|
||||
produced_hrs.precision, produced_hrs.value, produced_hrs.suffix,
|
||||
cShare);
|
||||
} else {
|
||||
if (fCtx->nbFilesTotal > 1) {
|
||||
size_t const srcFileNameSize = strlen(srcFileName);
|
||||
if (srcFileNameSize > 18) {
|
||||
const char* const truncatedSrcFileName = srcFileName + srcFileNameSize - 15;
|
||||
DISPLAY_PROGRESS("Compress: %u/%u files. Current: ...%s ",
|
||||
fCtx->currFileIdx+1, fCtx->nbFilesTotal,
|
||||
truncatedSrcFileName);
|
||||
} else {
|
||||
DISPLAY_PROGRESS("Compress: %u/%u files. Current: %*s ",
|
||||
fCtx->currFileIdx+1, fCtx->nbFilesTotal,
|
||||
(int)(18-srcFileNameSize), srcFileName);
|
||||
}
|
||||
}
|
||||
DISPLAY_PROGRESS("Read:%6.*f%4s ", consumed_hrs.precision,
|
||||
consumed_hrs.value, consumed_hrs.suffix);
|
||||
if (context->srcFileSize != UTIL_FILESIZE_UNKNOWN)
|
||||
DISPLAY_PROGRESS("/%6.*f%4s", context->fileHrs.precision,
|
||||
context->fileHrs.value, context->fileHrs.suffix);
|
||||
DISPLAY_PROGRESS(" ==> %2.f%%", cShare);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long long
|
||||
@@ -2155,9 +2229,89 @@ FIO_rust_compressZstdCallback(void* fCtx, void* prefs, void* ress,
|
||||
const char* srcFileName, U64 srcFileSize,
|
||||
int compressionLevel, U64* readsize)
|
||||
{
|
||||
return FIO_compressZstdFrame((FIO_ctx_t*)fCtx, (FIO_prefs_t*)prefs,
|
||||
(const cRess_t*)ress, srcFileName,
|
||||
srcFileSize, compressionLevel, readsize);
|
||||
FIO_ctx_t* const fCtxPtr = (FIO_ctx_t*)fCtx;
|
||||
FIO_prefs_t* const prefsPtr = (FIO_prefs_t*)prefs;
|
||||
cRess_t const* const ressPtr = (const cRess_t*)ress;
|
||||
FIO_rust_zstd_projection_context_t context;
|
||||
FIO_rust_zstd_compress_projection_t projection;
|
||||
U64 compressedSize = 0;
|
||||
U64 pledgedSrcSize = ZSTD_CONTENTSIZE_UNKNOWN;
|
||||
size_t zstdResult = 0;
|
||||
int status;
|
||||
|
||||
memset(&context, 0, sizeof(context));
|
||||
context.fCtx = fCtxPtr;
|
||||
context.prefs = prefsPtr;
|
||||
context.cctx = ressPtr->cctx;
|
||||
context.lastAdaptTime = UTIL_getTime();
|
||||
context.srcFileSize = srcFileSize;
|
||||
context.fileHrs = UTIL_makeHumanReadableSize(srcFileSize);
|
||||
|
||||
memset(&projection, 0, sizeof(projection));
|
||||
projection.readOpaque = (void*)ressPtr->readCtx;
|
||||
projection.writeOpaque = (void*)ressPtr->writeCtx;
|
||||
projection.codecOpaque = &context;
|
||||
projection.policyOpaque = &context;
|
||||
projection.readBufferSize = ZSTD_CStreamInSize();
|
||||
projection.readFill = FIO_rust_zstd_readFill;
|
||||
projection.readConsume = FIO_rust_zstd_readConsume;
|
||||
projection.writeAcquire = FIO_rust_zstd_writeAcquire;
|
||||
projection.writeEnqueue = FIO_rust_zstd_writeEnqueue;
|
||||
projection.writeRelease = FIO_rust_zstd_writeRelease;
|
||||
projection.sparseWriteEnd = FIO_rust_zstd_sparseWriteEnd;
|
||||
projection.compressStream = FIO_rust_zstd_compressStream;
|
||||
projection.iteration = FIO_rust_zstd_iteration;
|
||||
|
||||
DISPLAYLEVEL(6, "compression using zstd format \n");
|
||||
|
||||
/* Keep pledged-size and memory diagnostics in C while Rust owns the
|
||||
* surrounding asynchronous stream loop. */
|
||||
if (srcFileSize != UTIL_FILESIZE_UNKNOWN) {
|
||||
pledgedSrcSize = srcFileSize;
|
||||
CHECK(ZSTD_CCtx_setPledgedSrcSize(ressPtr->cctx, srcFileSize));
|
||||
} else if (prefsPtr->streamSrcSize > 0) {
|
||||
/* unknown source size; use the declared stream size */
|
||||
pledgedSrcSize = prefsPtr->streamSrcSize;
|
||||
CHECK(ZSTD_CCtx_setPledgedSrcSize(ressPtr->cctx, prefsPtr->streamSrcSize));
|
||||
}
|
||||
|
||||
{ int windowLog;
|
||||
UTIL_HumanReadableSize_t windowSize;
|
||||
CHECK(ZSTD_CCtx_getParameter(ressPtr->cctx, ZSTD_c_windowLog, &windowLog));
|
||||
if (windowLog == 0) {
|
||||
if (prefsPtr->ldmFlag) {
|
||||
/* If long mode is set without a window size libzstd will set this size internally */
|
||||
windowLog = ZSTD_WINDOWLOG_LIMIT_DEFAULT;
|
||||
} else {
|
||||
ZSTD_compressionParameters const cParams =
|
||||
ZSTD_getCParams(compressionLevel, srcFileSize, 0);
|
||||
windowLog = (int)cParams.windowLog;
|
||||
}
|
||||
}
|
||||
windowSize = UTIL_makeHumanReadableSize(
|
||||
MAX(1ULL, MIN(1ULL << windowLog, pledgedSrcSize)));
|
||||
DISPLAYLEVEL(4, "Decompression will require %.*f%s of memory\n",
|
||||
windowSize.precision, windowSize.value, windowSize.suffix);
|
||||
}
|
||||
|
||||
status = FIO_rust_compressZstdFrame(
|
||||
&projection, srcFileName, srcFileSize, compressionLevel,
|
||||
readsize, &compressedSize, &zstdResult);
|
||||
switch (status) {
|
||||
case FIO_RUST_ZSTD_OK:
|
||||
return compressedSize;
|
||||
case FIO_RUST_ZSTD_COMPRESS_ERROR:
|
||||
DISPLAYLEVEL(5, "%s \n",
|
||||
"ZSTD_compressStream2(ress.cctx, &outBuff, &inBuff, directive)");
|
||||
EXM_THROW(11, "%s", ZSTD_getErrorName(zstdResult));
|
||||
case FIO_RUST_ZSTD_INCOMPLETE_INPUT:
|
||||
EXM_THROW(27, "Read error : Incomplete read : %llu / %llu B",
|
||||
(unsigned long long)*readsize,
|
||||
(unsigned long long)srcFileSize);
|
||||
default:
|
||||
assert(status == FIO_RUST_ZSTD_INVALID_PROJECTION);
|
||||
EXM_THROW(11, "zstd compression projection is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ZSTD_GZCOMPRESS
|
||||
|
||||
@@ -259,6 +259,60 @@ pub struct FIO_rust_compress_callbacks_t {
|
||||
pub display_status: Option<FIO_rust_compress_status_display_fn>,
|
||||
}
|
||||
|
||||
pub const FIO_RUST_ZSTD_OK: c_int = 0;
|
||||
pub const FIO_RUST_ZSTD_COMPRESS_ERROR: c_int = 1;
|
||||
pub const FIO_RUST_ZSTD_INCOMPLETE_INPUT: c_int = 2;
|
||||
pub const FIO_RUST_ZSTD_INVALID_PROJECTION: c_int = 3;
|
||||
|
||||
const FIO_RUST_ZSTD_E_CONTINUE: c_int = 0;
|
||||
const FIO_RUST_ZSTD_E_END: c_int = 2;
|
||||
const UTIL_FILESIZE_UNKNOWN: u64 = u64::MAX;
|
||||
|
||||
pub type FIO_rust_zstd_read_fill_fn =
|
||||
unsafe extern "C" fn(*mut c_void, usize, *mut *const u8, *mut usize) -> usize;
|
||||
pub type FIO_rust_zstd_read_consume_fn = unsafe extern "C" fn(*mut c_void, usize);
|
||||
pub type FIO_rust_zstd_write_acquire_fn =
|
||||
unsafe extern "C" fn(*mut c_void, *mut *mut c_void, *mut *mut u8, *mut usize);
|
||||
pub type FIO_rust_zstd_write_enqueue_fn =
|
||||
unsafe extern "C" fn(*mut c_void, *mut *mut c_void, usize, *mut *mut u8, *mut usize);
|
||||
pub type FIO_rust_zstd_write_release_fn = unsafe extern "C" fn(*mut c_void, *mut c_void);
|
||||
pub type FIO_rust_zstd_sparse_write_end_fn = unsafe extern "C" fn(*mut c_void);
|
||||
pub type FIO_rust_zstd_compress_stream_fn = unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
*const c_char,
|
||||
c_int,
|
||||
*const u8,
|
||||
usize,
|
||||
usize,
|
||||
*mut u8,
|
||||
usize,
|
||||
*mut usize,
|
||||
*mut usize,
|
||||
*mut usize,
|
||||
*mut usize,
|
||||
) -> c_int;
|
||||
pub type FIO_rust_zstd_iteration_fn =
|
||||
unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_int, usize, usize, usize);
|
||||
|
||||
/// Rust owns the zstd read/compress/write loop. C keeps the zstd context,
|
||||
/// adaptive policy, diagnostics, and codec calls behind opaque callbacks.
|
||||
#[repr(C)]
|
||||
pub struct FIO_rust_zstd_compress_projection_t {
|
||||
pub read_opaque: *mut c_void,
|
||||
pub write_opaque: *mut c_void,
|
||||
pub codec_opaque: *mut c_void,
|
||||
pub policy_opaque: *mut c_void,
|
||||
pub read_buffer_size: usize,
|
||||
pub read_fill: Option<FIO_rust_zstd_read_fill_fn>,
|
||||
pub read_consume: Option<FIO_rust_zstd_read_consume_fn>,
|
||||
pub write_acquire: Option<FIO_rust_zstd_write_acquire_fn>,
|
||||
pub write_enqueue: Option<FIO_rust_zstd_write_enqueue_fn>,
|
||||
pub write_release: Option<FIO_rust_zstd_write_release_fn>,
|
||||
pub sparse_write_end: Option<FIO_rust_zstd_sparse_write_end_fn>,
|
||||
pub compress_stream: Option<FIO_rust_zstd_compress_stream_fn>,
|
||||
pub iteration: Option<FIO_rust_zstd_iteration_fn>,
|
||||
}
|
||||
|
||||
pub const FIO_RUST_GZIP_OK: c_int = 0;
|
||||
pub const FIO_RUST_GZIP_INIT_ERROR: c_int = 1;
|
||||
pub const FIO_RUST_GZIP_DEFLATE_ERROR: c_int = 2;
|
||||
@@ -1973,6 +2027,196 @@ pub unsafe extern "C" fn FIO_rust_compressFilenameInternal(
|
||||
FIO_RUST_COMPRESS_OK
|
||||
}
|
||||
|
||||
/// Compresses one zstd frame through the C-owned zstd context and adaptive
|
||||
/// policy. Rust owns the stream loop and exact pool accounting; C callbacks
|
||||
/// retain `ZSTD_compressStream2()`, diagnostics, and all private CLI state.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn FIO_rust_compressZstdFrame(
|
||||
projection: *const FIO_rust_zstd_compress_projection_t,
|
||||
src_file_name: *const c_char,
|
||||
src_file_size: u64,
|
||||
compression_level: c_int,
|
||||
read_size: *mut u64,
|
||||
compressed_size: *mut u64,
|
||||
zstd_result: *mut usize,
|
||||
) -> c_int {
|
||||
assert!(!projection.is_null());
|
||||
assert!(!src_file_name.is_null());
|
||||
assert!(!read_size.is_null());
|
||||
assert!(!compressed_size.is_null());
|
||||
assert!(!zstd_result.is_null());
|
||||
|
||||
unsafe {
|
||||
*read_size = 0;
|
||||
*compressed_size = 0;
|
||||
*zstd_result = 0;
|
||||
}
|
||||
|
||||
let projection = unsafe { &*projection };
|
||||
if projection.read_buffer_size == 0 {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
}
|
||||
let Some(read_fill) = projection.read_fill else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
let Some(read_consume) = projection.read_consume else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
let Some(write_acquire) = projection.write_acquire else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
let Some(write_enqueue) = projection.write_enqueue else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
let Some(write_release) = projection.write_release else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
let Some(sparse_write_end) = projection.sparse_write_end else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
let Some(compress_stream) = projection.compress_stream else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
let Some(iteration) = projection.iteration else {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
};
|
||||
|
||||
let mut job = ptr::null_mut::<c_void>();
|
||||
let mut output = ptr::null_mut::<u8>();
|
||||
let mut output_size = 0_usize;
|
||||
unsafe {
|
||||
write_acquire(
|
||||
projection.write_opaque,
|
||||
&mut job,
|
||||
&mut output,
|
||||
&mut output_size,
|
||||
);
|
||||
}
|
||||
if job.is_null() || (output.is_null() && output_size != 0) {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
}
|
||||
|
||||
let mut input = ptr::null::<u8>();
|
||||
let mut input_size = 0_usize;
|
||||
let mut input_pos = 0_usize;
|
||||
let mut in_file_size = 0_u64;
|
||||
let mut out_file_size = 0_u64;
|
||||
let mut directive = FIO_RUST_ZSTD_E_CONTINUE;
|
||||
let mut compression_level = compression_level;
|
||||
|
||||
loop {
|
||||
if input_pos == input_size {
|
||||
let mut loaded = 0_usize;
|
||||
let added = unsafe {
|
||||
read_fill(
|
||||
projection.read_opaque,
|
||||
projection.read_buffer_size,
|
||||
&mut input,
|
||||
&mut loaded,
|
||||
)
|
||||
};
|
||||
if loaded != 0 && input.is_null() {
|
||||
return FIO_RUST_ZSTD_INVALID_PROJECTION;
|
||||
}
|
||||
input_size = loaded;
|
||||
input_pos = 0;
|
||||
in_file_size = in_file_size.wrapping_add(added as u64);
|
||||
unsafe { *read_size = in_file_size };
|
||||
|
||||
if loaded == 0
|
||||
|| (src_file_size != UTIL_FILESIZE_UNKNOWN && in_file_size == src_file_size)
|
||||
{
|
||||
directive = FIO_RUST_ZSTD_E_END;
|
||||
}
|
||||
}
|
||||
|
||||
let mut still_to_flush = 1_usize;
|
||||
while input_pos != input_size || (directive == FIO_RUST_ZSTD_E_END && still_to_flush != 0) {
|
||||
let old_input_pos = input_pos;
|
||||
let mut new_input_pos = input_pos;
|
||||
let mut output_produced = 0_usize;
|
||||
let mut to_flush_now = 0_usize;
|
||||
let mut codec_result = 0_usize;
|
||||
let status = unsafe {
|
||||
compress_stream(
|
||||
projection.codec_opaque,
|
||||
src_file_name,
|
||||
directive,
|
||||
input,
|
||||
input_size,
|
||||
input_pos,
|
||||
output,
|
||||
output_size,
|
||||
&mut new_input_pos,
|
||||
&mut output_produced,
|
||||
&mut to_flush_now,
|
||||
&mut codec_result,
|
||||
)
|
||||
};
|
||||
if status != 0 {
|
||||
unsafe {
|
||||
*zstd_result = codec_result;
|
||||
*read_size = in_file_size;
|
||||
*compressed_size = out_file_size;
|
||||
}
|
||||
return FIO_RUST_ZSTD_COMPRESS_ERROR;
|
||||
}
|
||||
still_to_flush = codec_result;
|
||||
assert!(new_input_pos >= old_input_pos);
|
||||
assert!(new_input_pos <= input_size);
|
||||
assert!(output_produced <= output_size);
|
||||
|
||||
unsafe { read_consume(projection.read_opaque, new_input_pos - old_input_pos) };
|
||||
input_pos = new_input_pos;
|
||||
|
||||
if output_produced != 0 {
|
||||
unsafe {
|
||||
write_enqueue(
|
||||
projection.write_opaque,
|
||||
&mut job,
|
||||
output_produced,
|
||||
&mut output,
|
||||
&mut output_size,
|
||||
);
|
||||
}
|
||||
out_file_size = out_file_size.wrapping_add(output_produced as u64);
|
||||
unsafe { *compressed_size = out_file_size };
|
||||
}
|
||||
|
||||
unsafe {
|
||||
iteration(
|
||||
projection.policy_opaque,
|
||||
src_file_name,
|
||||
&mut compression_level,
|
||||
old_input_pos,
|
||||
new_input_pos,
|
||||
to_flush_now,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
if directive == FIO_RUST_ZSTD_E_END {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if src_file_size != UTIL_FILESIZE_UNKNOWN && in_file_size != src_file_size {
|
||||
unsafe {
|
||||
*read_size = in_file_size;
|
||||
*compressed_size = out_file_size;
|
||||
}
|
||||
return FIO_RUST_ZSTD_INCOMPLETE_INPUT;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*read_size = in_file_size;
|
||||
*compressed_size = out_file_size;
|
||||
write_release(projection.write_opaque, job);
|
||||
sparse_write_end(projection.write_opaque);
|
||||
}
|
||||
FIO_RUST_ZSTD_OK
|
||||
}
|
||||
|
||||
/// Compresses one gzip member through the C-owned zlib and asynchronous
|
||||
/// resource callbacks. The loop mirrors the original `FIO_compressGzFrame`
|
||||
/// sequencing: input is counted when a read-pool buffer is loaded, consumed
|
||||
@@ -3837,6 +4081,300 @@ mod tests {
|
||||
assert_eq!(context.totalBytesOutput, 47);
|
||||
}
|
||||
|
||||
struct ZstdProjectionState {
|
||||
input: [u8; 5],
|
||||
input_pos: usize,
|
||||
output_buffer: [u8; 4],
|
||||
consumed: Vec<usize>,
|
||||
enqueue_sizes: Vec<usize>,
|
||||
output_chunks: Vec<Vec<u8>>,
|
||||
directives: Vec<c_int>,
|
||||
iterations: Vec<(usize, usize, usize, c_int)>,
|
||||
acquire_calls: usize,
|
||||
release_calls: usize,
|
||||
sparse_end_calls: usize,
|
||||
compress_calls: usize,
|
||||
codec_error: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for ZstdProjectionState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
input: *b"abcde",
|
||||
input_pos: 0,
|
||||
output_buffer: [0; 4],
|
||||
consumed: Vec::new(),
|
||||
enqueue_sizes: Vec::new(),
|
||||
output_chunks: Vec::new(),
|
||||
directives: Vec::new(),
|
||||
iterations: Vec::new(),
|
||||
acquire_calls: 0,
|
||||
release_calls: 0,
|
||||
sparse_end_calls: 0,
|
||||
compress_calls: 0,
|
||||
codec_error: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_read_fill(
|
||||
opaque: *mut c_void,
|
||||
requested: usize,
|
||||
buffer: *mut *const u8,
|
||||
loaded: *mut usize,
|
||||
) -> usize {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
let available = state.input.len() - state.input_pos;
|
||||
let amount = available.min(requested);
|
||||
unsafe {
|
||||
*buffer = state.input.as_ptr().add(state.input_pos);
|
||||
*loaded = amount;
|
||||
}
|
||||
amount
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_read_consume(opaque: *mut c_void, amount: usize) {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
assert!(amount <= state.input.len() - state.input_pos);
|
||||
state.input_pos += amount;
|
||||
state.consumed.push(amount);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_write_acquire(
|
||||
opaque: *mut c_void,
|
||||
job: *mut *mut c_void,
|
||||
buffer: *mut *mut u8,
|
||||
buffer_size: *mut usize,
|
||||
) {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
state.acquire_calls += 1;
|
||||
unsafe {
|
||||
*job = opaque;
|
||||
*buffer = state.output_buffer.as_mut_ptr();
|
||||
*buffer_size = state.output_buffer.len();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_write_enqueue(
|
||||
opaque: *mut c_void,
|
||||
job: *mut *mut c_void,
|
||||
used: usize,
|
||||
buffer: *mut *mut u8,
|
||||
buffer_size: *mut usize,
|
||||
) {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
assert_eq!(unsafe { *job }, opaque);
|
||||
assert!(used <= state.output_buffer.len());
|
||||
state.enqueue_sizes.push(used);
|
||||
state
|
||||
.output_chunks
|
||||
.push(state.output_buffer[..used].to_vec());
|
||||
unsafe {
|
||||
*buffer = state.output_buffer.as_mut_ptr();
|
||||
*buffer_size = state.output_buffer.len();
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_write_release(opaque: *mut c_void, job: *mut c_void) {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
assert_eq!(job, opaque);
|
||||
state.release_calls += 1;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_sparse_end(opaque: *mut c_void) {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
state.sparse_end_calls += 1;
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_compress(
|
||||
opaque: *mut c_void,
|
||||
_src_file_name: *const c_char,
|
||||
directive: c_int,
|
||||
_input: *const u8,
|
||||
input_size: usize,
|
||||
input_pos: usize,
|
||||
output: *mut u8,
|
||||
output_size: usize,
|
||||
input_pos_after: *mut usize,
|
||||
output_produced: *mut usize,
|
||||
to_flush_now: *mut usize,
|
||||
zstd_result: *mut usize,
|
||||
) -> c_int {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
state.compress_calls += 1;
|
||||
state.directives.push(directive);
|
||||
let remaining = input_size - input_pos;
|
||||
let consumed = remaining.min(2);
|
||||
let needs_final_flush = directive == FIO_RUST_ZSTD_E_END && consumed == remaining;
|
||||
let result = if needs_final_flush && remaining != 0 {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*input_pos_after = input_pos + consumed;
|
||||
*output_produced = 1;
|
||||
*to_flush_now = 1;
|
||||
*zstd_result = result;
|
||||
assert!(output_size != 0);
|
||||
*output = b'x';
|
||||
}
|
||||
if let Some(error) = state.codec_error {
|
||||
unsafe {
|
||||
*input_pos_after = input_pos;
|
||||
*output_produced = 0;
|
||||
*zstd_result = error;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
unsafe extern "C" fn zstd_test_iteration(
|
||||
opaque: *mut c_void,
|
||||
_src_file_name: *const c_char,
|
||||
compression_level: *mut c_int,
|
||||
old_input_pos: usize,
|
||||
new_input_pos: usize,
|
||||
to_flush_now: usize,
|
||||
) {
|
||||
let state = unsafe { &mut *opaque.cast::<ZstdProjectionState>() };
|
||||
state
|
||||
.iterations
|
||||
.push((old_input_pos, new_input_pos, to_flush_now, unsafe {
|
||||
*compression_level
|
||||
}));
|
||||
}
|
||||
|
||||
fn zstd_test_projection(
|
||||
state: &mut ZstdProjectionState,
|
||||
) -> FIO_rust_zstd_compress_projection_t {
|
||||
let opaque = (state as *mut ZstdProjectionState).cast::<c_void>();
|
||||
FIO_rust_zstd_compress_projection_t {
|
||||
read_opaque: opaque,
|
||||
write_opaque: opaque,
|
||||
codec_opaque: opaque,
|
||||
policy_opaque: opaque,
|
||||
read_buffer_size: 3,
|
||||
read_fill: Some(zstd_test_read_fill),
|
||||
read_consume: Some(zstd_test_read_consume),
|
||||
write_acquire: Some(zstd_test_write_acquire),
|
||||
write_enqueue: Some(zstd_test_write_enqueue),
|
||||
write_release: Some(zstd_test_write_release),
|
||||
sparse_write_end: Some(zstd_test_sparse_end),
|
||||
compress_stream: Some(zstd_test_compress),
|
||||
iteration: Some(zstd_test_iteration),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zstd_projection_preserves_stream_order_and_accounting() {
|
||||
let mut state = ZstdProjectionState::default();
|
||||
let projection = zstd_test_projection(&mut state);
|
||||
let mut read_size = 0;
|
||||
let mut compressed_size = 0;
|
||||
let mut zstd_result = 0;
|
||||
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
FIO_rust_compressZstdFrame(
|
||||
&projection,
|
||||
c"zstd-test".as_ptr(),
|
||||
5,
|
||||
3,
|
||||
&mut read_size,
|
||||
&mut compressed_size,
|
||||
&mut zstd_result,
|
||||
)
|
||||
},
|
||||
FIO_RUST_ZSTD_OK
|
||||
);
|
||||
assert_eq!(read_size, 5);
|
||||
assert_eq!(compressed_size, 4);
|
||||
assert_eq!(zstd_result, 0);
|
||||
assert_eq!(state.consumed, vec![2, 1, 2, 0]);
|
||||
assert_eq!(state.enqueue_sizes, vec![1, 1, 1, 1]);
|
||||
assert_eq!(state.output_chunks, vec![vec![b'x']; 4]);
|
||||
assert_eq!(state.compress_calls, 4);
|
||||
assert_eq!(
|
||||
state.directives,
|
||||
vec![
|
||||
FIO_RUST_ZSTD_E_CONTINUE,
|
||||
FIO_RUST_ZSTD_E_CONTINUE,
|
||||
FIO_RUST_ZSTD_E_END,
|
||||
FIO_RUST_ZSTD_E_END,
|
||||
]
|
||||
);
|
||||
assert_eq!(state.iterations.len(), 4);
|
||||
assert_eq!(state.acquire_calls, 1);
|
||||
assert_eq!(state.release_calls, 1);
|
||||
assert_eq!(state.sparse_end_calls, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zstd_projection_does_not_consume_input_after_codec_error() {
|
||||
let mut state = ZstdProjectionState {
|
||||
codec_error: Some(17),
|
||||
..ZstdProjectionState::default()
|
||||
};
|
||||
let projection = zstd_test_projection(&mut state);
|
||||
let mut read_size = 0;
|
||||
let mut compressed_size = 0;
|
||||
let mut zstd_result = 0;
|
||||
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
FIO_rust_compressZstdFrame(
|
||||
&projection,
|
||||
c"zstd-test".as_ptr(),
|
||||
5,
|
||||
3,
|
||||
&mut read_size,
|
||||
&mut compressed_size,
|
||||
&mut zstd_result,
|
||||
)
|
||||
},
|
||||
FIO_RUST_ZSTD_COMPRESS_ERROR
|
||||
);
|
||||
assert_eq!(zstd_result, 17);
|
||||
assert_eq!(read_size, 3);
|
||||
assert_eq!(compressed_size, 0);
|
||||
assert_eq!(state.compress_calls, 1);
|
||||
assert!(state.consumed.is_empty());
|
||||
assert_eq!(state.release_calls, 0);
|
||||
assert_eq!(state.sparse_end_calls, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zstd_projection_reports_incomplete_input_without_releasing_output() {
|
||||
let mut state = ZstdProjectionState::default();
|
||||
let projection = zstd_test_projection(&mut state);
|
||||
let mut read_size = 0;
|
||||
let mut compressed_size = 0;
|
||||
let mut zstd_result = 0;
|
||||
|
||||
assert_eq!(
|
||||
unsafe {
|
||||
FIO_rust_compressZstdFrame(
|
||||
&projection,
|
||||
c"zstd-test".as_ptr(),
|
||||
7,
|
||||
3,
|
||||
&mut read_size,
|
||||
&mut compressed_size,
|
||||
&mut zstd_result,
|
||||
)
|
||||
},
|
||||
FIO_RUST_ZSTD_INCOMPLETE_INPUT
|
||||
);
|
||||
assert_eq!(read_size, 5);
|
||||
assert_eq!(compressed_size, 4);
|
||||
assert_eq!(zstd_result, 0);
|
||||
assert_eq!(state.release_calls, 0);
|
||||
assert_eq!(state.sparse_end_calls, 0);
|
||||
}
|
||||
|
||||
struct GzipProjectionState {
|
||||
input: [u8; 5],
|
||||
input_pos: usize,
|
||||
|
||||
Reference in New Issue
Block a user