Fix async decompression of .7z files by implementing Memory<byte> ReadAsync overload#1114
Conversation
- Implemented ReadAsync and RefillCacheAsync methods in BufferedSubStream - Added async test cases for SevenZipArchive (LZMA, LZMA2, Solid, BZip2, PPMd) - Tests show LZMA, BZip2, and PPMd working correctly - LZMA2 and Solid archives still failing with Data Error - investigating cache state management Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
The issue was that .NET 10's ReadExactlyAsync calls the Memory<byte> overload of ReadAsync, which wasn't implemented in BufferedSubStream. This caused it to fall back to the base Stream implementation that uses synchronous reads, leading to cache state corruption. Solution: Added ValueTask<int> ReadAsync(Memory<byte>, CancellationToken) overload for modern .NET versions. All tests now passing including LZMA2 and Solid archives. Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
…te machine suspension
There was a problem hiding this comment.
Pull request overview
This PR fixes a critical bug where async extraction of .7z files with LZMA/LZMA2 compression threw DataErrorException, while synchronous extraction worked correctly. The root cause was that BufferedSubStream only implemented the legacy byte[] ReadAsync overload, causing .NET 6+ to fall back to synchronous reads when ReadExactlyAsync called the Memory<byte> overload. This corrupted cache state when LZMA's RangeCoder mixed synchronous ReadByte() calls with async operations.
The fix implements true async I/O by adding ReadAsync(Memory<byte>, CancellationToken) overloads throughout the decompression pipeline:
- Added
ReadAsync(Memory<byte>)andRefillCacheAsync()toBufferedSubStreamfor async cache management - Added
ReadAsync(Memory<byte>)toLzmaStreamwith proper async decompression handling - Enhanced
LzOutWindowwith Memory support and converted async helpers from Task to ValueTask - Added comprehensive async test coverage for LZMA, LZMA2, Solid, BZip2, and PPMd archives
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs | New test file adding async extraction tests for all 7z compression types (LZMA, LZMA2, Solid, BZip2, PPMd) |
| src/SharpCompress/IO/BufferedSubStream.cs | Added Memory ReadAsync overload and RefillCacheAsync for true async buffered reads |
| src/SharpCompress/IO/ReadOnlySubStream.cs | Minor refactoring of ReadAsync Memory implementation |
| src/SharpCompress/Compressors/LZMA/LzmaStream.cs | Added Memory ReadAsync overload; changed DecodeChunkHeaderAsync return type to ValueTask |
| src/SharpCompress/Compressors/LZMA/LzmaDecoder.cs | Changed CodeAsync return type from Task to ValueTask for better performance |
| src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs | Added Read(Memory) overload and converted async methods to ValueTask; optimized async pattern |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Async extraction of .7z files with LZMA/LZMA2 compression threw
DataErrorExceptionwhen usingCopyToAsync(), while synchronousCopyTo()worked correctly.Root Cause
In .NET 6+,
ReadExactlyAsynccallsReadAsync(Memory<byte>, CancellationToken).BufferedSubStreamonly implemented the legacybyte[]overload, causing the baseStreamclass to fall back to synchronous reads. This corrupted cache state when LZMA'sRangeCodermixed syncReadByte()calls with async operations.Changes
BufferedSubStream: AddedReadAsync(Memory<byte>, CancellationToken)andRefillCacheAsync()for true async I/OExample
The fix ensures async operations remain async throughout the decompression pipeline, preventing sync-over-async patterns.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.