Fixes Async Writing#1311
Conversation
Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/6e4dec7d-12cd-4044-9935-4d354b911d11 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
… writers and compressors Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/6e4dec7d-12cd-4044-9935-4d354b911d11 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
…writes in async code paths Agent-Logs-Url: https://github.com/adamhathcock/sharpcompress/sessions/6e4dec7d-12cd-4044-9935-4d354b911d11 Co-authored-by: adamhathcock <527620+adamhathcock@users.noreply.github.com>
…only-stream-writes
There was a problem hiding this comment.
Pull request overview
This PR modernizes async-writing behavior across multiple archive/compression writers (notably ZIP, TAR, and 7z) to better support async-only streams, adds async finalization APIs, and refactors TAR compression detection and writer construction to streamline options/provider usage.
Changes:
- Added/extended async disposal + async write paths for writers and compressor streams (ZIP/TAR/7z, plus several compressor primitives).
- Refactored TAR factory compression probing APIs to accept
IReaderOptions(instead of just provider registries) and improved async writer creation for compressed TAR outputs. - Expanded test coverage and test utilities to enforce “async-only” stream behavior and validate new async methods.
Reviewed changes
Copilot reviewed 47 out of 48 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/SharpCompress.Test/Zip/ZipMemoryArchiveWithCrcAsyncTests.cs | Switches writer usage to await using for async disposal correctness. |
| tests/SharpCompress.Test/Tar/TarWriterAsyncTests.cs | Uses await using and updates AsyncOnlyStream usage to control underlying disposal. |
| tests/SharpCompress.Test/Tar/TarArchiveAsyncTests.cs | Uses await using for async writer lifetime in TAR async tests. |
| tests/SharpCompress.Test/Streams/LzmaStreamAsyncTests.cs | Validates async disposal and async-only output stream behavior for LZMA encoding. |
| tests/SharpCompress.Test/Streams/LeaveOpenBehaviorTests.cs | Updates LZip tests to use new LZipStream.Create(...) factory API. |
| tests/SharpCompress.Test/Streams/DisposalTests.cs | Updates disposal tests to use new LZip creation API. |
| tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs | Strengthens mock to throw on sync APIs and adds disposeStream control. |
| tests/SharpCompress.Test/GZip/GZipWriterAsyncTests.cs | Updates writer lifetimes to await using for async disposal. |
| tests/SharpCompress.Test/GZip/AsyncTests.cs | Uses await using for async writer usage. |
| tests/SharpCompress.Test/BZip2/BZip2StreamAsyncTests.cs | Switches to FinishAsync in async BZip2 tests. |
| tests/SharpCompress.Test/ADCTest.cs | Adds CRC32 async write tests for Crc32Stream. |
| src/SharpCompress/Writers/Zip/ZipWriter.cs | Refactors header/footer writing to accept explicit streams; adds async-capable ZipWritingStream write/limit logic. |
| src/SharpCompress/Writers/Zip/ZipWriter.Async.cs | Adds async DisposeAsync, async header emission, and async entry writing path. |
| src/SharpCompress/Writers/Tar/TarWriter.cs | Refactors TAR writer initialization and disposal semantics; stream creation extracted. |
| src/SharpCompress/Writers/Tar/TarWriter.Async.cs | Adds async DisposeAsync and async TAR writing helpers. |
| src/SharpCompress/Writers/SevenZip/SevenZipWriter.cs | Makes 7z placeholder header lazy and introduces sync/async placeholder helpers. |
| src/SharpCompress/Writers/SevenZip/SevenZipWriter.Async.cs | Adds async DisposeAsync and async finalize/header writing path. |
| src/SharpCompress/Writers/IWriter.cs | Removes embedded IAsyncWriter definition (moved to its own file). |
| src/SharpCompress/Writers/IAsyncWriter.cs | Introduces standalone async writer interface. |
| src/SharpCompress/Writers/GZip/GZipWriter.cs | Adds async disposal path for GZip writer output stream. |
| src/SharpCompress/Writers/AbstractWriter.cs | Makes disposal respect LeaveStreamOpen and exposes _isDisposed to derived writers. |
| src/SharpCompress/Writers/AbstractWriter.Async.cs | Makes DisposeAsync virtual for derived overrides. |
| src/SharpCompress/Providers/IFinishable.cs | Adds FinishAsync for async finalization hooks. |
| src/SharpCompress/Providers/Default/LZipCompressionProvider.cs | Adds async create methods and switches to LZipStream.Create/CreateAsync. |
| src/SharpCompress/IO/CountingStream.cs | Adds async flush/write overrides that track written byte counts. |
| src/SharpCompress/Factories/TarWrapper.cs | Updates LZip wrapper to use new LZip factory APIs (sync + async). |
| src/SharpCompress/Factories/TarFactory.cs | Refactors compression probing to use reader options; implements async-compressed TAR writer construction. |
| src/SharpCompress/Crypto/Crc32Stream.cs | Adds async write/flush overrides to support async-only pipelines and new tests. |
| src/SharpCompress/Compressors/ZStandard/CompressionStream.cs | Adjusts async-disposal interface implementation for legacy TFMs. |
| src/SharpCompress/Compressors/ZStandard/CompressionStream.Async.cs | Fixes partial class declaration to avoid duplicate base type. |
| src/SharpCompress/Compressors/LZMA/LzmaStream.cs | Adds IAsyncDisposable to LZMA stream type. |
| src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs | Implements real async write + async disposal behavior for LZMA encoding streams. |
| src/SharpCompress/Compressors/LZMA/LzmaEncoder.cs | Adds async encoding primitives and async coding loop support. |
| src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs | Adds async writes and async disposal; async chunk flush path. |
| src/SharpCompress/Compressors/LZMA/LZipStream.cs | Makes constructor private and removes header write from ctor (moved to factory). |
| src/SharpCompress/Compressors/LZMA/LZipStream.Async.cs | Adds Create/CreateAsync and FinishAsync for LZip; async header write helper. |
| src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs | Improves async disposal compatibility across TFMs and stream types. |
| src/SharpCompress/Compressors/Deflate/GZipStream.cs | Refactors header writing into reusable builder and introduces async header emission. |
| src/SharpCompress/Compressors/Deflate/GZipStream.Async.cs | Uses async header emission and improves async disposal compatibility. |
| src/SharpCompress/Compressors/Deflate/DeflateStream.cs | Adds legacy-TFM async-disposable support. |
| src/SharpCompress/Compressors/Deflate/DeflateStream.Async.cs | Expands async disposal support for legacy TFMs. |
| src/SharpCompress/Compressors/BZip2/CBZip2OutputStream.cs | Defers header emission; adds async header write + async finish buffering strategy. |
| src/SharpCompress/Compressors/BZip2/BZip2Stream.cs | Adds FinishAsync forwarding. |
| src/SharpCompress/Compressors/BZip2/BZip2Stream.Async.cs | Adds async FinishAsync helper. |
| src/SharpCompress/Common/SevenZip/SevenZipStreamsCompressor.cs | Uses await using for async-disposable LZMA/LZMA2 encoder streams. |
| src/SharpCompress/Common/SevenZip/SevenZipSignatureHeader.cs | Adds async placeholder/final header writing and refactors shared header construction. |
| src/SharpCompress/Archives/Tar/TarArchive.Factory.cs | Updates TAR compression detection calls to pass ReaderOptions. |
| .opencode/package-lock.json | Adds dependency lockfile for .opencode package reproducibility. |
Files not reviewed (1)
- .opencode/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private void EnsurePlaceholderWritten() | ||
| { | ||
| if (!_placeholderWritten) | ||
| { | ||
| _placeholderWritten = true; | ||
| // Write placeholder signature header (32 bytes) - will be back-patched on finalize | ||
| SevenZipSignatureHeaderWriter.WritePlaceholder(OutputStream.NotNull()); | ||
| } | ||
| } |
There was a problem hiding this comment.
SevenZipWriter now writes the 7z signature header placeholder lazily, but FinalizeArchive()/WriteFinal always seek to position 0 and assume the 32-byte placeholder already exists. If the first operation is WriteDirectory (or if the writer is disposed without any Write/WriteAsync), the placeholder is never written, so finalization will back-patch over real data and compute a negative nextHeaderOffset (cast to ulong). Ensure the placeholder is written before any entry (including directory entries) and before finalization when no entries were written.
| private async Task EnsurePlaceholderWrittenAsync(CancellationToken cancellationToken) | ||
| { | ||
| if (!_placeholderWritten) | ||
| { | ||
| _placeholderWritten = true; | ||
| // Write placeholder signature header (32 bytes) - will be back-patched on finalize | ||
| await SevenZipSignatureHeaderWriter | ||
| .WritePlaceholderAsync(OutputStream.NotNull(), cancellationToken) | ||
| .ConfigureAwait(false); | ||
| } |
There was a problem hiding this comment.
EnsurePlaceholderWrittenAsync sets _placeholderWritten=true before awaiting the actual write. If the write is canceled or fails, the flag remains true and the archive will later finalize by back-patching a header that was never reserved (corrupt output). Set the flag only after the placeholder write completes successfully (or reset it on failure).
This pull request introduces several improvements and refactorings to the handling of TAR and 7z archives, focusing on modernizing async support and streamlining API usage. The main changes include updating the TAR archive factory to simplify how compression types are determined, adding asynchronous methods for writing 7z signature headers, and improving resource management in 7z compression streams.
Key changes:
TAR Archive Factory Refactoring
TarFactory.GetCompressionTypeandGetCompressionTypeAsyncinTarArchive.Factory.csto pass the fullReaderOptionsobject instead of just theProvidersproperty, simplifying the API and aligning with updated method signatures. [1] [2] [3] [4] [5] [6]7z Archive Async Support
WritePlaceholderAsyncandWriteFinalAsyncmethods toSevenZipSignatureHeader.csto allow asynchronous writing of 7z signature headers, supporting cancellation tokens and non-blocking I/O. [1] [2]BuildFinalHeadermethod to reduce code duplication and improve maintainability. [1] [2]7z Compression Stream Improvements
SevenZipStreamsCompressor.csto useawait usingforLzma2EncoderStreamand, where supported, forLzmaStream, ensuring proper disposal of async streams and compatibility with newer .NET versions. [1] [2]Dependency Management
.opencode/package-lock.jsonfile to track and lock dependencies for the.opencodepackage, ensuring reproducible builds and consistent environments.