From 3530dd9d68299e962ca5e2a0334f809eeb8ba766 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Thu, 28 May 2026 07:46:25 +0100 Subject: [PATCH] Fix merge --- src/SharpCompress/Common/Tar/TarFilePart.cs | 8 +- .../Common/Tar/TarHeaderFactory.Async.cs | 7 +- .../Common/Tar/TarHeaderFactory.cs | 3 +- .../Common/Tar/TarReadOnlySubStream.cs | 2 +- .../Common/Zip/SeekableZipFilePart.Async.cs | 91 +++++++++++++++++-- .../Common/Zip/SeekableZipFilePart.cs | 58 +++++++++++- src/SharpCompress/packages.lock.json | 12 +-- 7 files changed, 149 insertions(+), 32 deletions(-) diff --git a/src/SharpCompress/Common/Tar/TarFilePart.cs b/src/SharpCompress/Common/Tar/TarFilePart.cs index 8a4c39c9..75b0be73 100644 --- a/src/SharpCompress/Common/Tar/TarFilePart.cs +++ b/src/SharpCompress/Common/Tar/TarFilePart.cs @@ -63,7 +63,7 @@ internal sealed class TarFilePart : FilePart // Fall back to existing behavior for stream-based sources _seekableStream.Position = Header.DataStartPosition ?? 0; - return new TarReadOnlySubStream(_seekableStream, Header.Size, false); + return new TarReadOnlySubStream(_seekableStream, Header.Size); } return Header.PackedStream.NotNull(); } @@ -74,13 +74,9 @@ internal sealed class TarFilePart : FilePart { if (_seekableStream != null) { - var useSyncOverAsync = false; -#if LEGACY_DOTNET - useSyncOverAsync = true; -#endif _seekableStream.Position = Header.DataStartPosition ?? 0; return new ValueTask( - new TarReadOnlySubStream(_seekableStream, Header.Size, useSyncOverAsync) + new TarReadOnlySubStream(_seekableStream, Header.Size) ); } return new ValueTask(Header.PackedStream.NotNull()); diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs index 772a5665..460be1cc 100644 --- a/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.Async.cs @@ -44,14 +44,9 @@ internal static partial class TarHeaderFactory break; case StreamingMode.Streaming: { - var useSyncOverAsync = false; -#if LEGACY_DOTNET - useSyncOverAsync = true; -#endif header.PackedStream = new TarReadOnlySubStream( stream, - header.Size, - useSyncOverAsync + header.Size ); } break; diff --git a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs index df71351e..6053fa10 100644 --- a/src/SharpCompress/Common/Tar/TarHeaderFactory.cs +++ b/src/SharpCompress/Common/Tar/TarHeaderFactory.cs @@ -40,8 +40,7 @@ internal static partial class TarHeaderFactory { header.PackedStream = new TarReadOnlySubStream( stream, - header.Size, - false + header.Size ); } break; diff --git a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs index 987751a3..79a4c4a4 100644 --- a/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs +++ b/src/SharpCompress/Common/Tar/TarReadOnlySubStream.cs @@ -10,7 +10,7 @@ internal class TarReadOnlySubStream : Stream private bool _isDisposed; private long _amountRead; - public TarReadOnlySubStream(Stream stream, long bytesToRead, bool useSyncOverAsyncDispose) + public TarReadOnlySubStream(Stream stream, long bytesToRead) { _stream = stream; BytesLeftToRead = bytesToRead; diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs index 8fff8436..3b082a71 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.Async.cs @@ -1,26 +1,103 @@ +using System; using System.IO; using System.Threading; using System.Threading.Tasks; using SharpCompress.Common.Zip.Headers; +using SharpCompress.IO; namespace SharpCompress.Common.Zip; -internal partial class SeekableZipFilePart +internal partial class SeekableZipFilePart : IDisposable { + private readonly SemaphoreSlim _asyncHeaderSemaphore = new(1, 1); internal override async ValueTask GetCompressedStreamAsync( CancellationToken cancellationToken = default ) { if (!_isLocalHeaderLoaded) { - await LoadLocalHeaderAsync(cancellationToken).ConfigureAwait(false); - _isLocalHeaderLoaded = true; + await _asyncHeaderSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (!_isLocalHeaderLoaded) + { + await LoadLocalHeaderAsync(cancellationToken).ConfigureAwait(false); + _isLocalHeaderLoaded = true; + } + } + finally + { + _asyncHeaderSemaphore.Release(); + } } return await base.GetCompressedStreamAsync(cancellationToken).ConfigureAwait(false); } - private async ValueTask LoadLocalHeaderAsync(CancellationToken cancellationToken = default) => - Header = await _headerFactory - .GetLocalHeaderAsync(BaseStream, (DirectoryEntryHeader)Header) - .ConfigureAwait(false); + private async ValueTask LoadLocalHeaderAsync(CancellationToken cancellationToken = default) + { + // Use an independent stream for loading the header if multi-threading is enabled + Stream streamToUse = BaseStream; + bool disposeStream = false; + + if ( + BaseStream is SourceStream sourceStream + && sourceStream.IsFileMode + && sourceStream.ReaderOptions.EnableMultiThreadedExtraction + ) + { + var independentStream = sourceStream.CreateIndependentStream(0); + if (independentStream is not null) + { + streamToUse = independentStream; + disposeStream = true; + } + } + else + { + // Check if BaseStream wraps a FileStream + Stream? underlyingStream = BaseStream; + if (BaseStream is IStreamStack streamStack) + { + underlyingStream = streamStack.BaseStream(); + } + + if (underlyingStream is FileStream fileStream) + { + streamToUse = new FileStream( + fileStream.Name, + FileMode.Open, + FileAccess.Read, + FileShare.Read + ); + disposeStream = true; + } + } + + try + { + Header = await _headerFactory.GetLocalHeaderAsync( + streamToUse, + (DirectoryEntryHeader)Header + ).ConfigureAwait(false); + } + finally + { + if (disposeStream) + { + if (streamToUse is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync().ConfigureAwait(false); + } + else + { +#pragma warning disable VSTHRD103 + streamToUse.Dispose(); +#pragma warning restore VSTHRD103 + } + } + } + } + + public void Dispose() => _asyncHeaderSemaphore.Dispose(); + } diff --git a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs index 3262020a..7e797fac 100644 --- a/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs +++ b/src/SharpCompress/Common/Zip/SeekableZipFilePart.cs @@ -1,6 +1,6 @@ using System.IO; using SharpCompress.Common.Zip.Headers; -using SharpCompress.Compressors; +using SharpCompress.IO; using SharpCompress.Providers; namespace SharpCompress.Common.Zip; @@ -10,7 +10,6 @@ internal partial class SeekableZipFilePart : ZipFilePart private volatile bool _isLocalHeaderLoaded; private readonly SeekableZipHeaderFactory _headerFactory; private readonly object _headerLock = new(); - private readonly SemaphoreSlim _asyncHeaderSemaphore = new(1, 1); internal SeekableZipFilePart( SeekableZipHeaderFactory headerFactory, @@ -36,8 +35,59 @@ internal partial class SeekableZipFilePart : ZipFilePart return base.GetCompressedStream(); } - private void LoadLocalHeader() => - Header = _headerFactory.GetLocalHeader(BaseStream, (DirectoryEntryHeader)Header); + private void LoadLocalHeader() + { + // Use an independent stream for loading the header if multi-threading is enabled + Stream streamToUse = BaseStream; + bool disposeStream = false; + + if ( + BaseStream is SourceStream sourceStream + && sourceStream.IsFileMode + && sourceStream.ReaderOptions.EnableMultiThreadedExtraction + ) + { + var independentStream = sourceStream.CreateIndependentStream(0); + if (independentStream is not null) + { + streamToUse = independentStream; + disposeStream = true; + } + } + else + { + // Check if BaseStream wraps a FileStream + Stream? underlyingStream = BaseStream; + if (BaseStream is IStreamStack streamStack) + { + underlyingStream = streamStack.BaseStream(); + } + + if (underlyingStream is FileStream fileStream) + { + streamToUse = new FileStream( + fileStream.Name, + FileMode.Open, + FileAccess.Read, + FileShare.Read + ); + disposeStream = true; + } + } + + try + { + Header = _headerFactory.GetLocalHeader(streamToUse, (DirectoryEntryHeader)Header); + } + finally + { + if (disposeStream) + { + streamToUse.Dispose(); + } + } + } + protected override Stream CreateBaseStream() { diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index e1e7afa5..738df599 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -286,9 +286,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.8, )", - "resolved": "10.0.8", - "contentHash": "dVbSXGIFNR5nZcv2tOLoWI+a9T4jtFd77IYjuND+QVe360qWgAF7H0WtoopYhRw/+SgpGUTyrkrh+65+ClNnfw==" + "requested": "[10.0.6, )", + "resolved": "10.0.6", + "contentHash": "QKuvS0LWX4fjFqeDkyM7Kqt8P3wYTiPD4nwU+9y59n0sCiG714fxDgbbN82vDnzq89AF/PiHl92TP2C4aFDUQA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -388,9 +388,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.27, )", - "resolved": "8.0.27", - "contentHash": "rQi9TxifHRnXP7lVRZH05DxD2/XGbJp12q0ozcbrlBlBnyyzssFTH/2vLhtKWUp2CT1qVscTrcYTFiwTyKPKRg==" + "requested": "[8.0.26, )", + "resolved": "8.0.26", + "contentHash": "o7/yVssM2r9Wyln2s9edBd5ANZXqdSdBI+g7JqXkyJmXrhs2WsJp25K5yPnYrTgdKBCjKB8bg+O2oew4sgzFaA==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct",