@@ -21,11 +21,10 @@ use http::header::{
2121} ;
2222use std:: fmt:: Write as _;
2323
24- #[ cfg( feature = "with-async-std" ) ]
25- use async_std:: stream:: Stream ;
26-
27- #[ cfg( feature = "with-tokio" ) ]
28- use tokio_stream:: Stream ;
24+ #[ cfg( any( feature = "with-tokio" , feature = "with-async-std" ) ) ]
25+ use futures_util:: Stream ;
26+ #[ cfg( any( feature = "with-tokio" , feature = "with-async-std" ) ) ]
27+ use futures_util:: stream:: { self , StreamExt } ;
2928
3029#[ derive( Debug ) ]
3130
@@ -126,28 +125,39 @@ impl tokio::io::AsyncRead for ResponseDataStream {
126125 cx : & mut std:: task:: Context < ' _ > ,
127126 buf : & mut tokio:: io:: ReadBuf < ' _ > ,
128127 ) -> std:: task:: Poll < std:: io:: Result < ( ) > > {
129- // Poll the stream for the next chunk of bytes
130- match Stream :: poll_next ( self . bytes . as_mut ( ) , cx) {
131- std:: task:: Poll :: Ready ( Some ( Ok ( chunk) ) ) => {
132- // Write as much of the chunk as fits in the buffer
133- let amt = std:: cmp:: min ( chunk. len ( ) , buf. remaining ( ) ) ;
134- buf. put_slice ( & chunk[ ..amt] ) ;
135-
136- // AIDEV-NOTE: Bytes that don't fit in the buffer are discarded from this chunk.
137- // This is expected AsyncRead behavior - consumers should use appropriately sized
138- // buffers or wrap in BufReader for efficiency with small reads.
139-
140- std:: task:: Poll :: Ready ( Ok ( ( ) ) )
141- }
142- std:: task:: Poll :: Ready ( Some ( Err ( error) ) ) => {
143- // Convert S3Error to io::Error
144- std:: task:: Poll :: Ready ( Err ( std:: io:: Error :: other ( error) ) )
145- }
146- std:: task:: Poll :: Ready ( None ) => {
147- // Stream is exhausted, signal EOF by returning Ok(()) with no bytes written
148- std:: task:: Poll :: Ready ( Ok ( ( ) ) )
128+ if buf. remaining ( ) == 0 {
129+ return std:: task:: Poll :: Ready ( Ok ( ( ) ) ) ;
130+ }
131+
132+ loop {
133+ match Stream :: poll_next ( self . bytes . as_mut ( ) , cx) {
134+ std:: task:: Poll :: Ready ( Some ( Ok ( chunk) ) ) => {
135+ if chunk. is_empty ( ) {
136+ continue ;
137+ }
138+
139+ let amt = std:: cmp:: min ( chunk. len ( ) , buf. remaining ( ) ) ;
140+ buf. put_slice ( & chunk[ ..amt] ) ;
141+
142+ if amt < chunk. len ( ) {
143+ let remainder = chunk. slice ( amt..) ;
144+ let previous_stream =
145+ std:: mem:: replace ( & mut self . bytes , Box :: pin ( stream:: empty ( ) ) ) ;
146+ self . bytes = Box :: pin (
147+ stream:: once ( async move { Ok ( remainder) } ) . chain ( previous_stream) ,
148+ ) ;
149+ }
150+
151+ return std:: task:: Poll :: Ready ( Ok ( ( ) ) ) ;
152+ }
153+ std:: task:: Poll :: Ready ( Some ( Err ( error) ) ) => {
154+ return std:: task:: Poll :: Ready ( Err ( std:: io:: Error :: other ( error) ) ) ;
155+ }
156+ std:: task:: Poll :: Ready ( None ) => {
157+ return std:: task:: Poll :: Ready ( Ok ( ( ) ) ) ;
158+ }
159+ std:: task:: Poll :: Pending => return std:: task:: Poll :: Pending ,
149160 }
150- std:: task:: Poll :: Pending => std:: task:: Poll :: Pending ,
151161 }
152162 }
153163}
@@ -159,28 +169,39 @@ impl async_std::io::Read for ResponseDataStream {
159169 cx : & mut std:: task:: Context < ' _ > ,
160170 buf : & mut [ u8 ] ,
161171 ) -> std:: task:: Poll < std:: io:: Result < usize > > {
162- // Poll the stream for the next chunk of bytes
163- match Stream :: poll_next ( self . bytes . as_mut ( ) , cx) {
164- std:: task:: Poll :: Ready ( Some ( Ok ( chunk) ) ) => {
165- // Write as much of the chunk as fits in the buffer
166- let amt = std:: cmp:: min ( chunk. len ( ) , buf. len ( ) ) ;
167- buf[ ..amt] . copy_from_slice ( & chunk[ ..amt] ) ;
168-
169- // AIDEV-NOTE: Bytes that don't fit in the buffer are discarded from this chunk.
170- // This is expected AsyncRead behavior - consumers should use appropriately sized
171- // buffers or wrap in BufReader for efficiency with small reads.
172-
173- std:: task:: Poll :: Ready ( Ok ( amt) )
174- }
175- std:: task:: Poll :: Ready ( Some ( Err ( error) ) ) => {
176- // Convert S3Error to io::Error
177- std:: task:: Poll :: Ready ( Err ( std:: io:: Error :: other ( error) ) )
178- }
179- std:: task:: Poll :: Ready ( None ) => {
180- // Stream is exhausted, signal EOF by returning 0 bytes read
181- std:: task:: Poll :: Ready ( Ok ( 0 ) )
172+ if buf. is_empty ( ) {
173+ return std:: task:: Poll :: Ready ( Ok ( 0 ) ) ;
174+ }
175+
176+ loop {
177+ match Stream :: poll_next ( self . bytes . as_mut ( ) , cx) {
178+ std:: task:: Poll :: Ready ( Some ( Ok ( chunk) ) ) => {
179+ if chunk. is_empty ( ) {
180+ continue ;
181+ }
182+
183+ let amt = std:: cmp:: min ( chunk. len ( ) , buf. len ( ) ) ;
184+ buf[ ..amt] . copy_from_slice ( & chunk[ ..amt] ) ;
185+
186+ if amt < chunk. len ( ) {
187+ let remainder = chunk. slice ( amt..) ;
188+ let previous_stream =
189+ std:: mem:: replace ( & mut self . bytes , Box :: pin ( stream:: empty ( ) ) ) ;
190+ self . bytes = Box :: pin (
191+ stream:: once ( async move { Ok ( remainder) } ) . chain ( previous_stream) ,
192+ ) ;
193+ }
194+
195+ return std:: task:: Poll :: Ready ( Ok ( amt) ) ;
196+ }
197+ std:: task:: Poll :: Ready ( Some ( Err ( error) ) ) => {
198+ return std:: task:: Poll :: Ready ( Err ( std:: io:: Error :: other ( error) ) ) ;
199+ }
200+ std:: task:: Poll :: Ready ( None ) => {
201+ return std:: task:: Poll :: Ready ( Ok ( 0 ) ) ;
202+ }
203+ std:: task:: Poll :: Pending => return std:: task:: Poll :: Pending ,
182204 }
183- std:: task:: Poll :: Pending => std:: task:: Poll :: Pending ,
184205 }
185206 }
186207}
@@ -875,11 +896,9 @@ mod tests {
875896 }
876897
877898 #[ tokio:: test]
878- async fn test_async_read_with_small_buffer ( ) {
879- // Create a stream with a large chunk
880- let chunks = vec ! [ Ok ( Bytes :: from(
881- "This is a much longer string that won't fit in a small buffer" ,
882- ) ) ] ;
899+ async fn test_async_read_with_small_buffer_preserves_large_chunk ( ) {
900+ let expected = b"This is a much longer string that won't fit in a small buffer" ;
901+ let chunks = vec ! [ Ok ( Bytes :: copy_from_slice( expected) ) ] ;
883902
884903 let stream = stream:: iter ( chunks) ;
885904 let data_stream: DataStream = Box :: pin ( stream) ;
@@ -889,17 +908,18 @@ mod tests {
889908 status_code : 200 ,
890909 } ;
891910
892- // Read with a small buffer - demonstrates that excess bytes are discarded per chunk
911+ let mut output = Vec :: new ( ) ;
893912 let mut buffer = [ 0u8 ; 10 ] ;
894- let n = response_stream. read ( & mut buffer) . await . unwrap ( ) ;
895913
896- // We should only get the first 10 bytes
897- assert_eq ! ( n, 10 ) ;
898- assert_eq ! ( & buffer[ ..n] , b"This is a " ) ;
914+ loop {
915+ let n = response_stream. read ( & mut buffer) . await . unwrap ( ) ;
916+ if n == 0 {
917+ break ;
918+ }
919+ output. extend_from_slice ( & buffer[ ..n] ) ;
920+ }
899921
900- // Next read should get 0 bytes (EOF) because the chunk was consumed
901- let n = response_stream. read ( & mut buffer) . await . unwrap ( ) ;
902- assert_eq ! ( n, 0 ) ;
922+ assert_eq ! ( output, expected) ;
903923 }
904924
905925 #[ tokio:: test]
@@ -992,11 +1012,9 @@ mod async_std_tests {
9921012 }
9931013
9941014 #[ async_std:: test]
995- async fn test_async_read_with_small_buffer ( ) {
996- // Create a stream with a large chunk
997- let chunks = vec ! [ Ok ( Bytes :: from(
998- "This is a much longer string that won't fit in a small buffer" ,
999- ) ) ] ;
1015+ async fn test_async_read_with_small_buffer_preserves_large_chunk ( ) {
1016+ let expected = b"This is a much longer string that won't fit in a small buffer" ;
1017+ let chunks = vec ! [ Ok ( Bytes :: copy_from_slice( expected) ) ] ;
10001018
10011019 let stream = stream:: iter ( chunks) ;
10021020 let data_stream: DataStream = Box :: pin ( stream) ;
@@ -1006,17 +1024,18 @@ mod async_std_tests {
10061024 status_code : 200 ,
10071025 } ;
10081026
1009- // Read with a small buffer - demonstrates that excess bytes are discarded per chunk
1027+ let mut output = Vec :: new ( ) ;
10101028 let mut buffer = [ 0u8 ; 10 ] ;
1011- let n = response_stream. read ( & mut buffer) . await . unwrap ( ) ;
10121029
1013- // We should only get the first 10 bytes
1014- assert_eq ! ( n, 10 ) ;
1015- assert_eq ! ( & buffer[ ..n] , b"This is a " ) ;
1030+ loop {
1031+ let n = response_stream. read ( & mut buffer) . await . unwrap ( ) ;
1032+ if n == 0 {
1033+ break ;
1034+ }
1035+ output. extend_from_slice ( & buffer[ ..n] ) ;
1036+ }
10161037
1017- // Next read should get 0 bytes (EOF) because the chunk was consumed
1018- let n = response_stream. read ( & mut buffer) . await . unwrap ( ) ;
1019- assert_eq ! ( n, 0 ) ;
1038+ assert_eq ! ( output, expected) ;
10201039 }
10211040
10221041 #[ async_std:: test]
0 commit comments