Skip to content

Commit

Permalink
set a minimal size in the compression output buffer (#3961)
Browse files Browse the repository at this point in the history
it should be at least large enough to hold the 10 byte gzip header.
Otherwise the compressor goes into an infinite loop
  • Loading branch information
Geal authored Oct 6, 2023
1 parent 3eb70b7 commit 80c2dd3
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .changesets/fix_geal_compression_minimal_output_size.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### Fix hang and high CPU usage when compressing small responses ([PR #3961](https://github.com/apollographql/router/pull/3961))

When returning small responses (less than 10 bytes) and compressing them using gzip, the router could go into an infinite loop

---

By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/3961
27 changes: 26 additions & 1 deletion apollo-router/src/axum_factory/compression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub(crate) mod codec;
pub(crate) mod unshared;
pub(crate) mod util;

const GZIP_HEADER_LEN: usize = 10;

pub(crate) enum Compressor {
Deflate(DeflateEncoder),
Gzip(GzipEncoder),
Expand Down Expand Up @@ -79,7 +81,9 @@ where {
}
}
Ok(data) => {
let mut buf = BytesMut::zeroed(data.len());
// the buffer needs at least 10 bytes for a gzip header if we use gzip, then more
// room to store the data itself
let mut buf = BytesMut::zeroed(GZIP_HEADER_LEN + data.len());

let mut partial_input = PartialBuffer::new(&*data);
let mut partial_output = PartialBuffer::new(&mut buf);
Expand Down Expand Up @@ -230,6 +234,27 @@ mod tests {
assert!(stream.next().await.is_none());
}

#[tokio::test]
async fn small_input() {
let compressor = Compressor::new(["gzip"].into_iter()).unwrap();

let body: Body = vec![0u8, 1, 2, 3].into();

let mut stream = compressor.process(body);
let mut decoder = GzipDecoder::new(Vec::new());

while let Some(buf) = stream.next().await {
let b = buf.unwrap();
decoder.write_all(&b).await.unwrap();
}

decoder.shutdown().await.unwrap();
let response = decoder.into_inner();
assert_eq!(response, [0u8, 1, 2, 3]);

assert!(stream.next().await.is_none());
}

#[tokio::test]
async fn gzip_header_writing() {
let compressor = Compressor::new(["gzip"].into_iter()).unwrap();
Expand Down

0 comments on commit 80c2dd3

Please sign in to comment.