Skip to content

Commit 9df508b

Browse files
authored
Fix update of bytes read in the encoder state machine. (#456)
Previously we were not updating the number of bytes read: we were just changing the local binding of `read`. This means that read was always zero and therefore the encoder never flushed. Fixes #455
1 parent 0370b47 commit 9df508b

2 files changed

Lines changed: 77 additions & 5 deletions

File tree

crates/async-compression/src/generic/bufread/encoder.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,10 @@ impl Encoder {
3434
mut input: Option<&mut PartialBuffer<&[u8]>>,
3535
) -> ControlFlow<Result<()>> {
3636
loop {
37-
self.state = match self.state {
38-
State::Encoding(mut read) => match input.as_mut() {
37+
self.state = match &mut self.state {
38+
State::Encoding(read) => match input.as_mut() {
3939
None => {
40-
if read == 0 {
40+
if *read == 0 {
4141
if output.written().is_empty() {
4242
// Poll for more data
4343
break;
@@ -56,7 +56,7 @@ impl Encoder {
5656
return ControlFlow::Break(Err(err));
5757
}
5858

59-
read += input.written().len();
59+
*read += input.written().len();
6060

6161
// Poll for more data
6262
break;
@@ -181,7 +181,11 @@ macro_rules! impl_encoder {
181181
}
182182

183183
if is_pending {
184-
return Poll::Pending;
184+
if output.written().is_empty() {
185+
return Poll::Pending;
186+
} else {
187+
return Poll::Ready(Ok(()));
188+
}
185189
}
186190
}
187191
}

crates/async-compression/tests/gzip.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,71 @@ fn gzip_bufread_chunks_decompress_with_extra_header() {
5151

5252
assert_eq!(output, &[1, 2, 3, 4, 5, 6][..]);
5353
}
54+
55+
#[test]
56+
#[ntest::timeout(1000)]
57+
#[cfg(feature = "futures-io")]
58+
fn gzip_bufread_chunks_compress_flushes_when_reader_pending() {
59+
use crate::utils::block_on;
60+
use async_compression::futures::bufread::GzipEncoder;
61+
use futures::AsyncRead;
62+
use futures::AsyncReadExt;
63+
use std::pin::Pin;
64+
use std::sync::atomic::{AtomicUsize, Ordering};
65+
use std::sync::Arc;
66+
use std::task::{Context, Poll};
67+
68+
struct Input {
69+
remaining: usize,
70+
chunks_sent: Arc<AtomicUsize>,
71+
chunks_received: Arc<AtomicUsize>,
72+
}
73+
74+
impl AsyncRead for Input {
75+
fn poll_read(
76+
self: Pin<&mut Self>,
77+
_cx: &mut Context<'_>,
78+
buf: &mut [u8],
79+
) -> Poll<std::io::Result<usize>> {
80+
if self.chunks_received.load(Ordering::Relaxed)
81+
< self.chunks_sent.load(Ordering::Relaxed)
82+
{
83+
Poll::Pending
84+
} else {
85+
let bytes = b"X".repeat(buf.len().min(64).min(self.remaining));
86+
buf[..bytes.len()].copy_from_slice(&bytes);
87+
let this = self.get_mut();
88+
this.remaining -= bytes.len();
89+
this.chunks_sent.fetch_add(1, Ordering::Relaxed);
90+
Poll::Ready(Ok(bytes.len()))
91+
}
92+
}
93+
}
94+
95+
let chunks_sent = Arc::new(AtomicUsize::new(0));
96+
let chunks_received = Arc::new(AtomicUsize::new(0));
97+
98+
let input = futures::io::BufReader::new(Input {
99+
remaining: 4 * 1024,
100+
chunks_sent: Arc::clone(&chunks_sent),
101+
chunks_received: Arc::clone(&chunks_received),
102+
});
103+
104+
let mut encoder = GzipEncoder::new(input);
105+
106+
let mut encoded_buffer: [u8; 64] = [0; 64];
107+
108+
block_on(async {
109+
while let Ok(read) = encoder.read(&mut encoded_buffer).await {
110+
if read == 0 {
111+
break;
112+
}
113+
chunks_received.fetch_add(1, Ordering::Relaxed);
114+
}
115+
});
116+
117+
assert_eq!(
118+
chunks_sent.load(Ordering::Relaxed),
119+
chunks_received.load(Ordering::Relaxed)
120+
);
121+
}

0 commit comments

Comments
 (0)