Skip to content

Only fall back to a whole-blob download if the blob cannot be split - #30742

Closed
fmeum wants to merge 2 commits into
bazelbuild:masterfrom
fmeum:fix-chunked-download-partial-write
Closed

Only fall back to a whole-blob download if the blob cannot be split#30742
fmeum wants to merge 2 commits into
bazelbuild:masterfrom
fmeum:fix-chunked-download-partial-write

Conversation

@fmeum

@fmeum fmeum commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Description

CombinedCache#downloadBlobFromRemote falls back to downloading a blob as a whole when its chunked download fails with a CacheNotFoundException. That exception covers two very different situations:

  • the server cannot describe the blob as a sequence of chunks, which is raised by getChunkDigests before any data has been produced, and
  • one of the chunks is missing from the CAS, which is only discovered after the preceding chunks have already been written to the output stream.

ChunkedBlobDownloader writes each chunk to the output stream as soon as it arrives and the stream cannot be rewound, so in the second case the fallback appended the entire blob to the chunks already written. Nothing catches this afterwards: there is no size or digest check over the final artifact, and --remote_verify_downloads doesn't help because the fallback wraps the stream in a fresh DigestOutputStream that only digests what the fallback itself writes. The result is a build artifact that is silently longer than its digest claims. The same applies to the other consumers of this code path, including Tree protos, in-memory outputs and stdout/stderr.

The fallback is now keyed off a dedicated BlobNotSplittableException, which is only thrown before any chunk data has been produced. A missing chunk keeps propagating as a CacheNotFoundException and is handled like any other missing blob, so the outcome no longer depends on which chunk happens to be missing.

SplitBlob returning UNIMPLEMENTED is now treated as "not splittable" as well. It previously turned into a plain IOException, so a server that advertises the parameters of a chunking function in its capabilities without implementing SplitBlob failed the build instead of falling back.

Motivation

Chunks have independent CAS lifetimes from the blob they belong to, so a chunk being evicted between SplitBlob and the read of that chunk is exactly the situation this fallback exists to handle. Recovering from it by restarting the download is only safe before any chunk has been handed to the caller.

Found by inspection; requires --experimental_remote_cache_chunking.

Build API Changes

No

Checklist

  • I have added tests for the new use cases (if any).
  • I have updated the documentation (if applicable).

Release Notes

RELNOTES: None

### Description

`CombinedCache#downloadBlobFromRemote` falls back to downloading a blob as a whole when its chunked download fails with a `CacheNotFoundException`. That exception covers two very different situations:

* the server cannot describe the blob as a sequence of chunks, which is raised by `getChunkDigests` before any data has been produced, and
* one of the chunks is missing from the CAS, which is only discovered after the preceding chunks have already been written to the output stream.

`ChunkedBlobDownloader` writes each chunk to the output stream as soon as it arrives and the stream cannot be rewound, so in the second case the fallback appended the entire blob to the chunks already written. Nothing catches this afterwards: there is no size or digest check over the final artifact, and `--remote_verify_downloads` doesn't help because the fallback wraps the stream in a fresh `DigestOutputStream` that only digests what the fallback itself writes. The result is a build artifact that is silently longer than its digest claims. The same applies to the other consumers of this code path, including `Tree` protos, in-memory outputs and stdout/stderr.

The fallback is now keyed off a dedicated `BlobNotSplittableException`, which is only thrown before any chunk data has been produced. A missing chunk keeps propagating as a `CacheNotFoundException` and is handled like any other missing blob, so the outcome no longer depends on which chunk happens to be missing.

`SplitBlob` returning `UNIMPLEMENTED` is now treated as "not splittable" as well. It previously turned into a plain `IOException`, so a server that advertises the parameters of a chunking function in its capabilities without implementing `SplitBlob` failed the build instead of falling back.

### Motivation

Chunks have independent CAS lifetimes from the blob they belong to, so a chunk being evicted between `SplitBlob` and the read of that chunk is exactly the situation this fallback exists to handle. Recovering from it by restarting the download is only safe before any chunk has been handed to the caller.

Found by inspection; requires `--experimental_remote_cache_chunking`.

### Build API Changes

No

### Checklist

- [x] I have added tests for the new use cases (if any).
- [ ] I have updated the documentation (if applicable).

### Release Notes

RELNOTES: None
@fmeum
fmeum force-pushed the fix-chunked-download-partial-write branch from e2a4cee to 9e1f98c Compare August 17, 2026 08:40
@fmeum fmeum changed the title Don't restart a chunked download into a partially written stream Only fall back to a whole-blob download if the blob cannot be split Aug 17, 2026
@fmeum
fmeum marked this pull request as ready for review August 18, 2026 09:16
@fmeum
fmeum requested a review from a team as a code owner August 18, 2026 09:16
@fmeum
fmeum requested a review from tjgq August 18, 2026 09:16
@github-actions github-actions Bot added team-Remote-Exec Issues and PRs for the Execution (Remote) team awaiting-review PR is awaiting review from an assigned reviewer labels Aug 18, 2026
@fmeum

fmeum commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

@bazel-io fork 9.3.0

@fmeum
fmeum requested a review from tjgq August 19, 2026 14:09
@tjgq tjgq added awaiting-PR-merge PR has been approved by a reviewer and is ready to be merge internally and removed awaiting-review PR is awaiting review from an assigned reviewer labels Aug 19, 2026
@github-actions github-actions Bot removed the awaiting-PR-merge PR has been approved by a reviewer and is ready to be merge internally label Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team-Remote-Exec Issues and PRs for the Execution (Remote) team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants