Skip to content

OpenAsyncReader, OpenAsyncArchive and others must be async for Tar detection#1210

Merged
adamhathcock merged 6 commits intomasterfrom
adam/issue-1206
Feb 12, 2026
Merged

OpenAsyncReader, OpenAsyncArchive and others must be async for Tar detection#1210
adamhathcock merged 6 commits intomasterfrom
adam/issue-1206

Conversation

@adamhathcock
Copy link
Owner

addresses #1206

Tar detection was done wrong and not in all code paths. This means that Open/Create for asynchronous paths must be asynchronous which breaks the API unfortunately

This pull request refactors the async archive opening APIs across the codebase to use ValueTask<T> instead of returning the archive type directly, and adds support for cancellation tokens. It also updates related interfaces and documentation to reflect these changes, and ensures that async APIs can be properly awaited and canceled. Additionally, the documentation and sample usages are updated to use await using for asynchronous disposables.

API Modernization and Async Improvements

  • Refactored all OpenAsyncArchive methods in archive factories (such as GZipArchive, RarArchive, SevenZipArchive) to return ValueTask<T> and accept a CancellationToken, enabling more efficient async usage and cancellation support. (src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs [1] [2]; src/SharpCompress/Archives/Rar/RarArchive.Factory.cs [3] [4]; src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs [5] [6]
  • Updated interfaces (IArchiveFactory, IArchiveOpenable, IMultiArchiveOpenable, IWritableArchiveOpenable) to reflect the new async signatures using ValueTask<T> and to accept CancellationToken. (src/SharpCompress/Archives/IArchiveFactory.cs [1] [2]; src/SharpCompress/Archives/IArchiveOpenable.cs [3]; src/SharpCompress/Archives/IMultiArchiveOpenable.cs [4]; src/SharpCompress/Archives/IWritableArchiveOpenable.cs [5]

Documentation and Sample Usage Updates

  • Updated documentation and code samples in docs/API.md to use await using for asynchronous disposables, reflecting the new async API usage patterns. [1] [2] [3]

Code Cleanup and Consistency

  • Removed unused using directives and made minor cleanups in several files for consistency and clarity. (src/SharpCompress/Archives/ArchiveFactory.cs [1] src/SharpCompress/Archives/Tar/TarArchive.Factory.cs [2]
  • Refactored TarArchive.Factory to call the multi-file overload for consistency. (src/SharpCompress/Archives/Tar/TarArchive.Factory.cs src/SharpCompress/Archives/Tar/TarArchive.Factory.csL40-L45)

These changes modernize the async API surface, improve cancellation support, and align documentation and usage with current C# best practices.

Copilot AI review requested due to automatic review settings February 12, 2026 10:34
@kilo-code-bot
Copy link
Contributor

kilo-code-bot bot commented Feb 12, 2026

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Overview

This PR introduces significant improvements to the async API consistency across SharpCompress:

  1. Async method signatures now return ValueTask<T> instead of T or IAsyncArchive directly, enabling proper async/await patterns with cancellation token support.

  2. Added CancellationToken parameters to all OpenAsyncArchive and OpenAsyncReader methods across all archive types (Zip, Rar, Tar, GZip, SevenZip).

  3. Fixed missing await calls in ArchiveFactory.Async.cs where OpenAsyncArchive was being called without awaiting the result.

  4. Updated documentation (docs/API.md) to use await using instead of using for async archive/reader instances, which is the correct pattern for IAsyncDisposable resources.

  5. Added compressed tar auto-detection in TarReader.OpenReader() and TarReader.OpenAsyncReader() methods, supporting GZip, BZip2, ZStandard, and LZip compression types.

  6. Added new test cases for non-seekable stream handling and compressed tar auto-detection.

Files Reviewed (30+ files)

Archive Factory Changes:

  • src/SharpCompress/Archives/ArchiveFactory.Async.cs - Fixed missing awaits
  • src/SharpCompress/Archives/IArchiveFactory.cs - Updated interface signatures
  • src/SharpCompress/Archives/IArchiveOpenable.cs - Updated interface signatures

Archive Type-Specific Changes:

  • src/SharpCompress/Archives/GZip/GZipArchive.Factory.cs
  • src/SharpCompress/Archives/Tar/TarArchive.Factory.cs
  • src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs
  • src/SharpCompress/Archives/Rar/RarArchive.Factory.cs
  • src/SharpCompress/Archives/SevenZip/SevenZipArchive.Factory.cs

Reader Factory Changes:

  • src/SharpCompress/Readers/Tar/TarReader.Factory.cs - Added compression auto-detection
  • src/SharpCompress/Readers/Zip/ZipReader.Factory.cs
  • src/SharpCompress/Readers/Rar/RarReader.Factory.cs
  • src/SharpCompress/Readers/GZip/GZipReader.Factory.cs

Factory Implementations:

  • src/SharpCompress/Factories/TarFactory.cs
  • src/SharpCompress/Factories/GZipFactory.cs
  • src/SharpCompress/Factories/ZipFactory.cs
  • src/SharpCompress/Factories/RarFactory.cs
  • src/SharpCompress/Factories/SevenZipFactory.cs

Documentation:

  • docs/API.md

Test Updates:

  • Multiple test files updated to use await with OpenAsyncArchive calls
  • New tests added for stream handling and auto-detection

Quality Observations

  • All async methods properly accept and forward CancellationToken
  • Consistent use of cancellationToken.ThrowIfCancellationRequested() at entry points
  • Proper ConfigureAwait(false) usage in internal async calls
  • Test coverage includes both sync and async code paths
  • Documentation examples updated to reflect correct usage patterns

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request addresses issue #1206 where compressed TAR formats were broken when using ArchiveFactory.OpenArchive(). The root cause was that TAR detection within compressed streams (e.g., tar.gz, tar.bz2) was performed synchronously in async code paths, which doesn't work for streams that only support async reads (like AsyncOnlyStream).

The fix modernizes the async API surface by changing OpenAsyncArchive and OpenAsyncReader methods to return ValueTask<T> instead of directly returning the archive/reader instance. This allows the methods to perform actual async I/O during TAR format detection. Additionally, CancellationToken support is added throughout the async APIs.

Changes:

  • Modified all OpenAsyncArchive and OpenAsyncReader methods to return ValueTask<T> and accept CancellationToken parameters, enabling truly asynchronous TAR detection
  • Updated TarArchive to track compression type internally, allowing it to properly handle compressed TAR files by decompressing the underlying stream before reading entries
  • Added TarFactory.GetCompressionType/Async helper methods to detect TAR compression types synchronously and asynchronously
  • Updated documentation to use await using pattern for async archive operations

Reviewed changes

Copilot reviewed 43 out of 43 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/SharpCompress/Archives/Tar/TarArchive.cs Added _compressionType field and GetStream() method to decompress TAR streams based on detected compression type
src/SharpCompress/Archives/Tar/TarArchive.Factory.cs Updated OpenAsyncArchive overloads to use GetCompressionTypeAsync for TAR detection; changed return types to ValueTask<T>
src/SharpCompress/Factories/TarFactory.cs Added GetCompressionType/Async methods to detect compression type in TAR files; updated factory methods to properly await async archive creation
src/SharpCompress/Readers/Tar/TarReader.Factory.cs Moved TAR detection logic from TarReader.cs and made OpenAsyncReader truly async with TAR wrapper detection
src/SharpCompress/Archives/IArchiveFactory.cs Changed OpenAsyncArchive methods to return ValueTask<IAsyncArchive> and accept CancellationToken
src/SharpCompress/Archives/IArchiveOpenable.cs Updated interface to require ValueTask<TASync> return types with CancellationToken support
src/SharpCompress/Archives/IMultiArchiveOpenable.cs Updated multi-volume archive async methods to return ValueTask<TASync>
src/SharpCompress/Archives/IWritableArchiveOpenable.cs Changed CreateAsyncArchive to return ValueTask<T>
src/SharpCompress/Readers/IReaderOpenable.cs Updated reader interface async methods to return ValueTask<IAsyncReader> with cancellation support
src/SharpCompress/Factories/*.cs Updated all factory implementations (GZip, Rar, SevenZip, Zip, Lzw) to properly return ValueTask<T> and check cancellation tokens
src/SharpCompress/Readers/*/Reader.Factory.cs Updated all reader factory implementations to return ValueTask<IAsyncReader> with cancellation support
tests/SharpCompress.Test/**/*AsyncTests.cs Fixed all async test cases to properly await OpenAsyncArchive and OpenAsyncReader calls
tests/SharpCompress.Test/Tar/TarArchiveTests.cs Added tests for compressed TAR detection via ArchiveFactory.OpenArchive
docs/API.md Updated documentation examples to use await using pattern for async archives

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +131 to +155
public static async ValueTask<CompressionType> GetCompressionTypeAsync(
Stream stream,
CancellationToken cancellationToken = default
)
{
stream.Seek(0, SeekOrigin.Begin);
foreach (var wrapper in TarWrapper.Wrappers)
{
stream.Seek(0, SeekOrigin.Begin);
if (wrapper.IsMatch(stream))
{
stream.Seek(0, SeekOrigin.Begin);
var decompressedStream = wrapper.CreateStream(stream);
if (
await TarArchive
.IsTarFileAsync(decompressedStream, cancellationToken)
.ConfigureAwait(false)
)
{
return wrapper.CompressionType;
}
}
}
throw new InvalidFormatException("Not a tar file.");
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GetCompressionTypeAsync method has the same issue as the synchronous version - it doesn't restore the stream position if no wrapper matches or if a wrapper matches but the decompressed stream is not a tar file. The stream should be reset to the beginning after checking all wrappers without finding a match, and should be reset before continuing to the next wrapper if IsTarFileAsync returns false.

Copilot uses AI. Check for mistakes.
Comment on lines +112 to +129
public static CompressionType GetCompressionType(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
foreach (var wrapper in TarWrapper.Wrappers)
{
stream.Seek(0, SeekOrigin.Begin);
if (wrapper.IsMatch(stream))
{
stream.Seek(0, SeekOrigin.Begin);
var decompressedStream = wrapper.CreateStream(stream);
if (TarArchive.IsTarFile(decompressedStream))
{
return wrapper.CompressionType;
}
}
}
throw new InvalidFormatException("Not a tar file.");
}
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GetCompressionType method doesn't restore the stream position if no wrapper matches or if a wrapper matches but the decompressed stream is not a tar file. After checking all wrappers without finding a match, the stream position should be reset to the beginning. Similarly, if a wrapper matches but IsTarFile returns false, the stream should be reset before continuing to the next wrapper.

Copilot uses AI. Check for mistakes.
Comment on lines +617 to 620
var archive = await archiveFactory.OpenAsyncArchive(
new AsyncOnlyStream(stream),
readerOptions
)
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment to archive is useless, since its value is never read.

Copilot uses AI. Check for mistakes.
@adamhathcock adamhathcock merged commit 147dbc8 into master Feb 12, 2026
12 checks passed
@adamhathcock adamhathcock deleted the adam/issue-1206 branch February 12, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants