Skip to content

PatchCompressor: write the decompressed size into the zstd frame header - #26

Merged
Y-PLONI merged 2 commits into
Otzaria:masterfrom
palmoni5:fix/patch-zstd-content-size
Sep 7, 2026
Merged

PatchCompressor: write the decompressed size into the zstd frame header#26
Y-PLONI merged 2 commits into
Otzaria:masterfrom
palmoni5:fix/patch-zstd-content-size

Conversation

@palmoni5

@palmoni5 palmoni5 commented Sep 6, 2026

Copy link
Copy Markdown
Member

Problem

Users updating the library via delta patches hit:

שגיאה בהחלת העדכון
Invalid argument(s): Could not allocate 11694234840 bytes.

PatchCompressor used ZstdOutputStream, which never pledges a source size, so the produced .zst frames carry no content-size field. On the client, the zstandard FFI plugin sizes its output buffer from ZSTD_getFrameContentSize; when the field is missing it falls back to compressedSize * 20 in a single contiguous allocation. 11,694,234,840 = 584,711,742 × 20 exactly, i.e. a ~558 MB patch asked for ~10.9 GiB of RAM.

Fix

Compress through ZstdCompressCtx streaming with setPledgedSrcSize(fileSize) and EndDirective.END on the last chunk, so the exact decompressed size lands in the frame header. Memory stays bounded (1 MiB input chunks, zstd-recommended output buffer); level, worker count and the returned Result metadata are unchanged.

Tests

New PatchCompressorTest:

  • Zstd.getFrameContentSize equals the input size, single-threaded and with workers = 2
  • byte-exact round trip, compressed and uncompressed sha256 match
  • empty patch yields a valid frame with size 0

:generator-common:jvmTest passes (87 tests).

Related

The Otzaria client side is also being changed to download and decompress patches as a stream to disk, so large patches no longer sit in RAM regardless of the header. This PR makes existing clients (in-memory decompress) allocate the right size instead of 20× the compressed size.

🤖 Generated with Claude Code

ZstdOutputStream never pledges a source size, so patch .zst files carried no
content-size field. The Otzaria client decompresses through the zstandard FFI
plugin, which sizes its output buffer from ZSTD_getFrameContentSize and, when
the field is missing, falls back to compressedSize * 20 in one allocation.
A ~560 MB patch therefore requested 11,694,234,840 bytes and failed with
"Invalid argument(s): Could not allocate 11694234840 bytes".

Compress via ZstdCompressCtx streaming with setPledgedSrcSize(fileSize),
keeping memory bounded (1 MiB input chunks) and preserving level/workers.
Tests assert Zstd.getFrameContentSize equals the input size (single and
multi-threaded), a byte-exact round trip, matching sha256s, and the empty
patch case.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Y-PLONI
Y-PLONI merged commit e1987a9 into Otzaria:master Sep 7, 2026
1 check passed
@palmoni5
palmoni5 deleted the fix/patch-zstd-content-size branch September 7, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants