@@ -206,11 +206,11 @@ public override int Read(byte[] buffer, int offset, int count)
206206 {
207207 ValidateBufferState ( ) ;
208208
209- // Fill buffer if needed
209+ // Fill buffer if needed, handling short reads from underlying stream
210210 if ( _bufferedLength == 0 )
211211 {
212- _bufferedLength = Stream . Read ( _buffer ! , 0 , _bufferSize ) ;
213212 _bufferPosition = 0 ;
213+ _bufferedLength = FillBuffer ( _buffer ! , 0 , _bufferSize ) ;
214214 }
215215 int available = _bufferedLength - _bufferPosition ;
216216 int toRead = Math . Min ( count , available ) ;
@@ -222,11 +222,8 @@ public override int Read(byte[] buffer, int offset, int count)
222222 return toRead ;
223223 }
224224 // If buffer exhausted, refill
225- int r = Stream . Read ( _buffer ! , 0 , _bufferSize ) ;
226- if ( r == 0 )
227- return 0 ;
228- _bufferedLength = r ;
229225 _bufferPosition = 0 ;
226+ _bufferedLength = FillBuffer ( _buffer ! , 0 , _bufferSize ) ;
230227 if ( _bufferedLength == 0 )
231228 {
232229 return 0 ;
@@ -250,6 +247,31 @@ public override int Read(byte[] buffer, int offset, int count)
250247 }
251248 }
252249
250+ /// <summary>
251+ /// Fills the buffer by reading from the underlying stream, handling short reads.
252+ /// Implements the ReadFully pattern: reads in a loop until buffer is full or EOF is reached.
253+ /// </summary>
254+ /// <param name="buffer">Buffer to fill</param>
255+ /// <param name="offset">Offset in buffer (always 0 in current usage)</param>
256+ /// <param name="count">Number of bytes to read</param>
257+ /// <returns>Total number of bytes read (may be less than count if EOF is reached)</returns>
258+ private int FillBuffer ( byte [ ] buffer , int offset , int count )
259+ {
260+ // Implement ReadFully pattern but return the actual count read
261+ // This is the same logic as Utility.ReadFully but returns count instead of bool
262+ var total = 0 ;
263+ int read ;
264+ while ( ( read = Stream . Read ( buffer , offset + total , count - total ) ) > 0 )
265+ {
266+ total += read ;
267+ if ( total >= count )
268+ {
269+ return total ;
270+ }
271+ }
272+ return total ;
273+ }
274+
253275 public override long Seek ( long offset , SeekOrigin origin )
254276 {
255277 if ( _bufferingEnabled )
@@ -324,13 +346,12 @@ CancellationToken cancellationToken
324346 {
325347 ValidateBufferState ( ) ;
326348
327- // Fill buffer if needed
349+ // Fill buffer if needed, handling short reads from underlying stream
328350 if ( _bufferedLength == 0 )
329351 {
330- _bufferedLength = await Stream
331- . ReadAsync ( _buffer ! , 0 , _bufferSize , cancellationToken )
332- . ConfigureAwait ( false ) ;
333352 _bufferPosition = 0 ;
353+ _bufferedLength = await FillBufferAsync ( _buffer ! , 0 , _bufferSize , cancellationToken )
354+ . ConfigureAwait ( false ) ;
334355 }
335356 int available = _bufferedLength - _bufferPosition ;
336357 int toRead = Math . Min ( count , available ) ;
@@ -342,13 +363,9 @@ CancellationToken cancellationToken
342363 return toRead ;
343364 }
344365 // If buffer exhausted, refill
345- int r = await Stream
346- . ReadAsync ( _buffer ! , 0 , _bufferSize , cancellationToken )
347- . ConfigureAwait ( false ) ;
348- if ( r == 0 )
349- return 0 ;
350- _bufferedLength = r ;
351366 _bufferPosition = 0 ;
367+ _bufferedLength = await FillBufferAsync ( _buffer ! , 0 , _bufferSize , cancellationToken )
368+ . ConfigureAwait ( false ) ;
352369 if ( _bufferedLength == 0 )
353370 {
354371 return 0 ;
@@ -369,6 +386,38 @@ CancellationToken cancellationToken
369386 }
370387 }
371388
389+ /// <summary>
390+ /// Async version of FillBuffer. Implements the ReadFullyAsync pattern.
391+ /// Reads in a loop until buffer is full or EOF is reached.
392+ /// </summary>
393+ private async Task < int > FillBufferAsync (
394+ byte [ ] buffer ,
395+ int offset ,
396+ int count ,
397+ CancellationToken cancellationToken
398+ )
399+ {
400+ // Implement ReadFullyAsync pattern but return the actual count read
401+ // This is the same logic as Utility.ReadFullyAsync but returns count instead of bool
402+ var total = 0 ;
403+ int read ;
404+ while (
405+ (
406+ read = await Stream
407+ . ReadAsync ( buffer , offset + total , count - total , cancellationToken )
408+ . ConfigureAwait ( false )
409+ ) > 0
410+ )
411+ {
412+ total += read ;
413+ if ( total >= count )
414+ {
415+ return total ;
416+ }
417+ }
418+ return total ;
419+ }
420+
372421 public override async Task WriteAsync (
373422 byte [ ] buffer ,
374423 int offset ,
@@ -399,13 +448,15 @@ public override async ValueTask<int> ReadAsync(
399448 {
400449 ValidateBufferState ( ) ;
401450
402- // Fill buffer if needed
451+ // Fill buffer if needed, handling short reads from underlying stream
403452 if ( _bufferedLength == 0 )
404453 {
405- _bufferedLength = await Stream
406- . ReadAsync ( _buffer . AsMemory ( 0 , _bufferSize ) , cancellationToken )
407- . ConfigureAwait ( false ) ;
408454 _bufferPosition = 0 ;
455+ _bufferedLength = await FillBufferMemoryAsync (
456+ _buffer . AsMemory ( 0 , _bufferSize ) ,
457+ cancellationToken
458+ )
459+ . ConfigureAwait ( false ) ;
409460 }
410461 int available = _bufferedLength - _bufferPosition ;
411462 int toRead = Math . Min ( buffer . Length , available ) ;
@@ -417,13 +468,12 @@ public override async ValueTask<int> ReadAsync(
417468 return toRead ;
418469 }
419470 // If buffer exhausted, refill
420- int r = await Stream
421- . ReadAsync ( _buffer . AsMemory ( 0 , _bufferSize ) , cancellationToken )
422- . ConfigureAwait ( false ) ;
423- if ( r == 0 )
424- return 0 ;
425- _bufferedLength = r ;
426471 _bufferPosition = 0 ;
472+ _bufferedLength = await FillBufferMemoryAsync (
473+ _buffer . AsMemory ( 0 , _bufferSize ) ,
474+ cancellationToken
475+ )
476+ . ConfigureAwait ( false ) ;
427477 if ( _bufferedLength == 0 )
428478 {
429479 return 0 ;
@@ -442,6 +492,35 @@ public override async ValueTask<int> ReadAsync(
442492 }
443493 }
444494
495+ /// <summary>
496+ /// Async version of FillBuffer for Memory{byte}. Implements the ReadFullyAsync pattern.
497+ /// Reads in a loop until buffer is full or EOF is reached.
498+ /// </summary>
499+ private async ValueTask < int > FillBufferMemoryAsync (
500+ Memory < byte > buffer ,
501+ CancellationToken cancellationToken
502+ )
503+ {
504+ // Implement ReadFullyAsync pattern but return the actual count read
505+ var total = 0 ;
506+ int read ;
507+ while (
508+ (
509+ read = await Stream
510+ . ReadAsync ( buffer . Slice ( total ) , cancellationToken )
511+ . ConfigureAwait ( false )
512+ ) > 0
513+ )
514+ {
515+ total += read ;
516+ if ( total >= buffer . Length )
517+ {
518+ return total ;
519+ }
520+ }
521+ return total ;
522+ }
523+
445524 public override async ValueTask WriteAsync (
446525 ReadOnlyMemory < byte > buffer ,
447526 CancellationToken cancellationToken = default
0 commit comments