Skip to content

Commit a123e13

Browse files
muirdmmeta-codesync[bot]
authored andcommitted
http-client: work around bug decoding empty zstd responses
Summary: Work around a bug in the async-compression crate by handling empty zstd payloads specially instead of using the streaming decoder. D91803401 upgraded the async-compression Rust crate, introducing a bug where the streaming decoder does not handle empty streams. Me and claude came up with this workaround where we avoid the streaming decode when there is no data. I opened Nullus157/async-compression#444 for upstream fix. Reviewed By: genevievehelsel Differential Revision: D92462715 fbshipit-source-id: 947d70bb28b88510fbd0340b97a61a4e612081ff
1 parent 2dc9456 commit a123e13

1 file changed

Lines changed: 26 additions & 8 deletions

File tree

eden/scm/lib/http-client/src/response.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,12 +198,30 @@ impl AsyncBody {
198198
pub fn decoded(self) -> ByteStream {
199199
let Self { encoding, body } = self;
200200
stream::once(async move {
201-
Ok(match encoding? {
202-
Encoding::Identity => body.boxed(),
203-
Encoding::Brotli => decode!(BrotliDecoder, body),
204-
Encoding::Deflate => decode!(DeflateDecoder, body),
205-
Encoding::Gzip => decode!(GzipDecoder, body),
206-
Encoding::Zstd => decode!(ZstdDecoder, body),
201+
let encoding = encoding?;
202+
203+
// Handle empty streams specially to work around an async-compression bug
204+
// where all decoders fail on empty input.
205+
// https://github.com/Nullus157/async-compression/pull/444
206+
let mut body = body;
207+
let first_chunk = match body.next().await {
208+
None => {
209+
// Empty stream - return empty result
210+
return Ok(stream::empty().boxed());
211+
}
212+
Some(result) => result?,
213+
};
214+
215+
// Prepend first chunk back to stream
216+
let prefix = stream::once(future::ready(Ok(first_chunk)));
217+
let combined: ByteStream = prefix.chain(body).boxed();
218+
219+
Ok(match encoding {
220+
Encoding::Identity => combined,
221+
Encoding::Brotli => decode!(BrotliDecoder, combined),
222+
Encoding::Deflate => decode!(DeflateDecoder, combined),
223+
Encoding::Gzip => decode!(GzipDecoder, combined),
224+
Encoding::Zstd => decode!(ZstdDecoder, combined),
207225
other => {
208226
return Err(HttpClientError::BadResponse(anyhow!(
209227
"Unsupported Content-Encoding: {:?}",
@@ -405,8 +423,8 @@ mod tests {
405423

406424
mock.assert();
407425

408-
// FIXME: error decoding empty response
409-
assert!(res.into_body().decoded().try_concat().await.is_err());
426+
// We decoded the empty response body properly.
427+
assert!(res.into_body().decoded().try_concat().await?.is_empty());
410428

411429
Ok(())
412430
}

0 commit comments

Comments
 (0)