From 0a265a3553c9f760557f64347e7f82475b0d2819 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Tue, 3 Oct 2023 18:13:03 +0200 Subject: [PATCH 1/6] set a minimal size in the compression output buffer it should be at least large enough to hold the 10 byte gzip header --- apollo-router/src/axum_factory/compression/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apollo-router/src/axum_factory/compression/mod.rs b/apollo-router/src/axum_factory/compression/mod.rs index 0629dd836e..bec0637981 100644 --- a/apollo-router/src/axum_factory/compression/mod.rs +++ b/apollo-router/src/axum_factory/compression/mod.rs @@ -79,7 +79,7 @@ where { } } Ok(data) => { - let mut buf = BytesMut::zeroed(data.len()); + let mut buf = BytesMut::zeroed(std::cmp::max(10, data.len())); let mut partial_input = PartialBuffer::new(&*data); let mut partial_output = PartialBuffer::new(&mut buf); From cd8abe85d42ecbca8a79f1c762911f75dc3a8610 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Wed, 4 Oct 2023 14:59:44 +0200 Subject: [PATCH 2/6] add a test --- .../src/axum_factory/compression/mod.rs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apollo-router/src/axum_factory/compression/mod.rs b/apollo-router/src/axum_factory/compression/mod.rs index bec0637981..ab96be6fb8 100644 --- a/apollo-router/src/axum_factory/compression/mod.rs +++ b/apollo-router/src/axum_factory/compression/mod.rs @@ -230,6 +230,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(); From c09de3dc65e4235a3ed91ea78707518993e36333 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Wed, 4 Oct 2023 14:59:56 +0200 Subject: [PATCH 3/6] add a bit more room --- apollo-router/src/axum_factory/compression/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apollo-router/src/axum_factory/compression/mod.rs b/apollo-router/src/axum_factory/compression/mod.rs index ab96be6fb8..be332b8875 100644 --- a/apollo-router/src/axum_factory/compression/mod.rs +++ b/apollo-router/src/axum_factory/compression/mod.rs @@ -79,7 +79,7 @@ where { } } Ok(data) => { - let mut buf = BytesMut::zeroed(std::cmp::max(10, data.len())); + let mut buf = BytesMut::zeroed(10 + data.len()); let mut partial_input = PartialBuffer::new(&*data); let mut partial_output = PartialBuffer::new(&mut buf); From 2fc534243d2554ac1f39a6f9d42a53b74ea24e93 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 5 Oct 2023 09:32:47 +0200 Subject: [PATCH 4/6] add comment --- apollo-router/src/axum_factory/compression/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apollo-router/src/axum_factory/compression/mod.rs b/apollo-router/src/axum_factory/compression/mod.rs index be332b8875..2135ea592d 100644 --- a/apollo-router/src/axum_factory/compression/mod.rs +++ b/apollo-router/src/axum_factory/compression/mod.rs @@ -79,6 +79,8 @@ where { } } Ok(data) => { + // 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(10 + data.len()); let mut partial_input = PartialBuffer::new(&*data); From 374a8688cb6b34b927af839388e3dafd8335e72d Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 5 Oct 2023 18:36:23 +0200 Subject: [PATCH 5/6] use a const --- apollo-router/src/axum_factory/compression/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apollo-router/src/axum_factory/compression/mod.rs b/apollo-router/src/axum_factory/compression/mod.rs index 2135ea592d..ff00377042 100644 --- a/apollo-router/src/axum_factory/compression/mod.rs +++ b/apollo-router/src/axum_factory/compression/mod.rs @@ -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), @@ -81,7 +83,7 @@ where { Ok(data) => { // 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(10 + data.len()); + 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); From 799cb54c4cad4e546e6d506138a09d4edb13400f Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 5 Oct 2023 18:38:34 +0200 Subject: [PATCH 6/6] changeset --- .changesets/fix_geal_compression_minimal_output_size.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changesets/fix_geal_compression_minimal_output_size.md diff --git a/.changesets/fix_geal_compression_minimal_output_size.md b/.changesets/fix_geal_compression_minimal_output_size.md new file mode 100644 index 0000000000..479fb6c357 --- /dev/null +++ b/.changesets/fix_geal_compression_minimal_output_size.md @@ -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 \ No newline at end of file