diff --git a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs index a3c65a851..de0adc98b 100644 --- a/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs +++ b/src/SharpCompress/Archives/SevenZip/SevenZipArchive.cs @@ -182,15 +182,15 @@ protected override EntryStream GetEntryStream() ); } - // Wrap with SyncOnlyStream to work around LZMA async bugs - // Return a ReadOnlySubStream that reads from the shared folder stream return CreateEntryStream( - new SyncOnlyStream( - new ReadOnlySubStream(_currentFolderStream, entry.Size, leaveOpen: true) - ) + new ReadOnlySubStream(_currentFolderStream, entry.Size, leaveOpen: true) ); } + protected override ValueTask GetEntryStreamAsync( + CancellationToken cancellationToken = default + ) => new(GetEntryStream()); + public override void Dispose() { _currentFolderStream?.Dispose(); diff --git a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs index d60c7a74a..0cde6052d 100644 --- a/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/SevenZip/SevenZipArchiveAsyncTests.cs @@ -3,6 +3,8 @@ using System.Threading; using System.Threading.Tasks; using SharpCompress.Archives; +using SharpCompress.Archives.SevenZip; +using SharpCompress.Readers; using SharpCompress.Test.Mocks; using Xunit; @@ -224,4 +226,95 @@ public async Task SevenZipArchive_PPMd_AsyncStreamExtraction() VerifyFiles(); } + + [Fact] + public async Task SevenZipArchive_Solid_ExtractAllEntries_Contiguous_Async() + { + // This test verifies that solid archives iterate entries as contiguous streams + // rather than recreating the decompression stream for each entry + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); + await using var archive = SevenZipArchive.OpenAsyncArchive(testArchive); + Assert.True(((SevenZipArchive)archive).IsSolid); + + await using var reader = await archive.ExtractAllEntriesAsync(); + while (await reader.MoveToNextEntryAsync()) + { + if (!reader.Entry.IsDirectory) + { + await reader.WriteEntryToDirectoryAsync(SCRATCH_FILES_PATH); + } + } + + VerifyFiles(); + } + + [Fact] + public async Task SevenZipArchive_Solid_VerifyStreamReuse() + { + // This test verifies that the folder stream is reused within each folder + // and not recreated for each entry in solid archives + var testArchive = Path.Combine(TEST_ARCHIVES_PATH, "7Zip.solid.7z"); + await using var archive = SevenZipArchive.OpenAsyncArchive(testArchive); + Assert.True(((SevenZipArchive)archive).IsSolid); + + await using var reader = await archive.ExtractAllEntriesAsync(); + + var sevenZipReader = Assert.IsType(reader); + sevenZipReader.DiagnosticsEnabled = true; + + Stream? currentFolderStreamInstance = null; + object? currentFolder = null; + var entryCount = 0; + var entriesInCurrentFolder = 0; + var streamRecreationsWithinFolder = 0; + + while (await reader.MoveToNextEntryAsync()) + { + if (!reader.Entry.IsDirectory) + { + // Extract the entry to trigger GetEntryStream + using var entryStream = await reader.OpenEntryStreamAsync(); + var buffer = new byte[4096]; + while (entryStream.Read(buffer, 0, buffer.Length) > 0) + { + // Read the stream to completion + } + + entryCount++; + + var folderStream = sevenZipReader.DiagnosticsCurrentFolderStream; + var folder = sevenZipReader.DiagnosticsCurrentFolder; + + Assert.NotNull(folderStream); // Folder stream should exist + + // Check if we're in a new folder + if (currentFolder == null || !ReferenceEquals(currentFolder, folder)) + { + // Starting a new folder + currentFolder = folder; + currentFolderStreamInstance = folderStream; + entriesInCurrentFolder = 1; + } + else + { + // Same folder - verify stream wasn't recreated + entriesInCurrentFolder++; + + if (!ReferenceEquals(currentFolderStreamInstance, folderStream)) + { + // Stream was recreated within the same folder - this is the bug we're testing for! + streamRecreationsWithinFolder++; + } + + currentFolderStreamInstance = folderStream; + } + } + } + + // Verify we actually tested multiple entries + Assert.True(entryCount > 1, "Test should have multiple entries to verify stream reuse"); + + // The critical check: within a single folder, the stream should NEVER be recreated + Assert.Equal(0, streamRecreationsWithinFolder); // Folder stream should remain the same for all entries in the same folder + } }