Skip to content

Fix zstd upload decoder rejecting exact-buffer-multiple blobs - #2594

Closed
erneestoc wants to merge 1 commit into
TraceMachina:mainfrom
erneestoc:ec/fix-zstd-decode-exact-multiple
Closed

Fix zstd upload decoder rejecting exact-buffer-multiple blobs#2594
erneestoc wants to merge 1 commit into
TraceMachina:mainfrom
erneestoc:ec/fix-zstd-decode-exact-multiple

Conversation

@erneestoc

@erneestoc erneestoc commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Problem

The streaming zstd decoder for REAPI compressed-blobs uploads
(stream_decode_compressed_upload) misreports a fully-decoded stream as
truncated whenever the blob's decompressed size is an exact multiple of the
decoder output buffer size (DCtx::out_size(), 128KiB-class). The final
run fills the output buffer exactly with hint == 0 (frame complete and
flushed), but the drain loop treated a full output buffer as "may have more
to flush" and polled the finished decoder once more. A finished decoder
returns the input-size hint for a new frame header, so the post-EOF
frame_input_hint != 0 truncation check fires and the upload fails with
InvalidArgument ("stream ended in the middle of a zstd frame").

This is deterministic and hits spec-conforming clients today: a Bazel
upload with --remote_cache_compression fails for any blob whose
uncompressed size lands on the buffer-multiple boundary.

Fix

Exit the drain loop when the decoder reports the frame complete and fully
flushed (hint == 0), regardless of whether the output buffer was filled
exactly. One condition change plus an explanatory comment.

Verification

  • New regression test decode_accepts_exact_output_buffer_multiple
    (1MiB blob, exact multiple of the output buffer): fails without the
    fix
    (misreported as truncated), passes with it.
  • Suites: bazel //nativelink-service/... 14/14 including clippy pedantic
    • nightly rustfmt aspects; cargo check --tests --workspace clean;
      wire-compression cargo suite 10/10.

Found while building compressed-upload support for NativeLink's own
GrpcStore transfers, where round-trip tests hit the boundary case.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UXVtatcR9YMecBiu9RjwpC


This change is Reviewable

The streaming zstd upload decoder's inner loop kept polling after a frame
finished whenever the last run filled the output buffer exactly. Polling a
finished decoder returns the input-size hint for a NEW frame header, and
the post-EOF truncation check then misreported the fully-decoded stream as
ending mid-frame. Deterministic for any blob whose decompressed size is an
exact multiple of the decoder output buffer size, so a spec-conforming
compressed-blobs upload (e.g. Bazel with --remote_cache_compression) of
such a blob fails with InvalidArgument.

Exit the drain loop when the decoder reports the frame complete and
flushed (hint == 0), regardless of whether the output buffer was filled
exactly. Regression test fails without the fix and passes with it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXVtatcR9YMecBiu9RjwpC
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nativelink Ready Ready Preview, Comment Jul 22, 2026 2:04am
nativelink-aidm Ready Ready Preview, Comment Jul 22, 2026 2:04am

Request Review

@erneestoc

Copy link
Copy Markdown
Contributor Author

closed as it's in #2596

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.

1 participant