Skip to content

fix(service): run compressed upload/download streams off the blocking pool - #3

Closed
walter-zeromatter wants to merge 3 commits into
mainfrom
user/wgray/zstd-compression-hang
Closed

fix(service): run compressed upload/download streams off the blocking pool#3
walter-zeromatter wants to merge 3 commits into
mainfrom
user/wgray/zstd-compression-hang

Conversation

@walter-zeromatter

Copy link
Copy Markdown

Summary

  • Compressed download encode and upload decode each held a tokio blocking-pool thread for the stream's full lifetime (client drain rate), so concurrent slow streams could exhaust the pool and starve every other spawn_blocking user.
  • Both paths now drive zstd's raw streaming API as plain async futures — async recv/send, no thread parked on either channel. Wire format and validation (bomb rejection, size checks, digest checks) are unchanged.
  • Adds a regression test that holds more encode streams than the blocking pool has threads and asserts an unrelated spawn_blocking closure still runs.

Test plan

  • cargo test -p nativelink-service --test wire_compression_test — 10 passed, including the new hang regression test
  • cargo check -p nativelink-service --tests

🤖 Generated with Claude Code

walter-zeromatter and others added 3 commits July 16, 2026 17:52
… blocking pool

A compressed-download encode stream drains at the gRPC client's pace. The
current implementation holds one tokio blocking-pool thread per stream for
the stream's entire lifetime (BufChannelReader blocks waiting for input and
blocking_send parks until the client consumes), so N concurrent downloads
with slow consumers occupy N pool threads and every unrelated spawn_blocking
user queues behind them. This test wires encode streams the way
ByteStreamServer::inner_read_compressed does, holds more of them than the
pool has threads, and asserts a trivial unrelated spawn_blocking closure
still runs. It fails against the current thread-per-stream implementation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cking pool

stream_encode_compressed_download previously ran under spawn_blocking with a
blocking reader over raw_rx and blocking_send into the output channel, so one
tokio blocking-pool thread was parked per compressed download for the whole
stream at the client's drain rate. Concurrent downloads with slow consumers
could exhaust the pool (default cap 512), starving every other spawn_blocking
user — filesystem store I/O, upload decode, credential resolution — and the
per-thread zstd contexts, stacks, and buffers inflated memory accordingly.

The encoder is now an async fn driving zstd's raw streaming API
(ZSTD_compressStream via zstd::stream::raw::Encoder): async recv, inline
per-chunk encode (CPU bounded by the small channel chunk size), async send
for backpressure. No thread is held while waiting on either channel. The
wire format is unchanged: one well-formed zstd frame, identical to what
zstd::stream::read::Encoder emitted. The bytestream_server caller drops its
spawn_blocking wrapper; dropping the read stream still cancels the encode
because the future itself is dropped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ing pool

Same defect shape as the download encode path: stream_decode_compressed_upload
ran under spawn_blocking, blocking on compressed_rx for client bytes and
parking in blocking_send while the store drained, holding one blocking-pool
thread per compressed upload for the stream's lifetime.

The decoder is now an async fn driving zstd's raw streaming API
(ZSTD_decompressStream via zstd::stream::raw::Decoder) with async channel
sends. Validation is unchanged: the per-chunk decoded-size cap (bomb
rejection), the exact final-size check, and the digest check are preserved,
and a stream that ends mid-frame is rejected as InvalidArgument (previously
surfaced as a read error from the blocking decoder).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@walter-zeromatter

Copy link
Copy Markdown
Author

Fixed by a separate PR

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