From ee7f86c2ba7880252c67f2d509134ba90a1a1308 Mon Sep 17 00:00:00 2001 From: Muir Manders Date: Thu, 5 Feb 2026 17:23:45 -0800 Subject: [PATCH] Fix decoding of empty response streams. Previously we were returning "zstd stream did not finish", but now we notice we got no data and transition directly to the done state. --- crates/async-compression/Cargo.toml | 4 ++++ .../src/generic/bufread/decoder.rs | 12 +++++++++++- .../async-compression/tests/empty_stream.rs | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 crates/async-compression/tests/empty_stream.rs diff --git a/crates/async-compression/Cargo.toml b/crates/async-compression/Cargo.toml index 8671e5a1..d30004f2 100644 --- a/crates/async-compression/Cargo.toml +++ b/crates/async-compression/Cargo.toml @@ -139,6 +139,10 @@ required-features = ["zlib", "tokio"] name = "zstd_gzip" required-features = ["zstd", "gzip", "tokio"] +[[test]] +name = "empty_stream" +required-features = ["zstd", "tokio"] + [[example]] name = "lzma_filters" required-features = ["xz", "tokio"] diff --git a/crates/async-compression/src/generic/bufread/decoder.rs b/crates/async-compression/src/generic/bufread/decoder.rs index 9cad7380..0033f021 100644 --- a/crates/async-compression/src/generic/bufread/decoder.rs +++ b/crates/async-compression/src/generic/bufread/decoder.rs @@ -17,6 +17,7 @@ enum State { pub struct Decoder { state: State, multiple_members: bool, + received_data: bool, } impl Default for Decoder { @@ -24,6 +25,7 @@ impl Default for Decoder { Self { state: State::Decoding, multiple_members: false, + received_data: false, } } } @@ -48,8 +50,16 @@ impl Decoder { // reader has returned EOF. self.multiple_members = false; - State::Flushing + // Empty stream (no data received) - return empty output + if !self.received_data { + State::Done + } else { + State::Flushing + } } else { + if !input.unwritten().is_empty() { + self.received_data = true; + } match decoder.decode(input, output) { Ok(true) => State::Flushing, // ignore the first error, occurs when input is empty diff --git a/crates/async-compression/tests/empty_stream.rs b/crates/async-compression/tests/empty_stream.rs new file mode 100644 index 00000000..44dc7eba --- /dev/null +++ b/crates/async-compression/tests/empty_stream.rs @@ -0,0 +1,19 @@ +//! Test that bufread decoders handle empty input streams (immediate EOF). + +#[macro_use] +mod utils; + +#[tokio::test] +async fn zstd_empty_stream() { + use async_compression::tokio::bufread::ZstdDecoder; + use std::io::Cursor; + use tokio::io::AsyncReadExt; + + let empty: &[u8] = &[]; + let mut decoder = ZstdDecoder::new(Cursor::new(empty)); + let mut output = Vec::new(); + let result = decoder.read_to_end(&mut output).await; + // Empty input should return Ok(0), not error with "zstd stream did not finish" + assert!(result.is_ok(), "empty stream failed: {:?}", result); + assert!(output.is_empty()); +}