Skip to content

Zip async implementation #114421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 40 commits into from
Apr 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
2161c6a
ref: System.IO.Compression
carlossanlop Apr 16, 2025
4a70fb1
src: ZipHelper async
carlossanlop Apr 16, 2025
7e902b1
src: ZipBlocks async
carlossanlop Apr 16, 2025
be1ef0b
src: ZipArchiveEntry async
carlossanlop Apr 16, 2025
5939611
src: ZipArchive async
carlossanlop Apr 16, 2025
e63bfbb
src: System.IO.Compression.csproj
carlossanlop Apr 16, 2025
76e2d4e
ref: System.IO.Compression.ZipFile
carlossanlop Apr 16, 2025
d4c4fd9
src: ZipFile.Create async
carlossanlop Apr 16, 2025
b723b9f
src: ZipFile.Extract async
carlossanlop Apr 16, 2025
c7319fa
src: ZipFileExtensions.ZipArchive.Create async
carlossanlop Apr 16, 2025
d889936
src: ZipFileExtensions.ZipArchive.Extract async
carlossanlop Apr 16, 2025
b25e514
src: ZipFileExtensions.ZipArchiveEntry.Extract async
carlossanlop Apr 16, 2025
7848832
src: System.IO.Compression.ZipFile.csproj
carlossanlop Apr 16, 2025
822fe91
test: Common async
carlossanlop Apr 16, 2025
7f1d854
tests: ZipArchive async
carlossanlop Apr 16, 2025
cbaf380
tests: ZipTestHelper.ZipFile async
carlossanlop Apr 16, 2025
f0b7624
tests: ZipFile async
carlossanlop Apr 16, 2025
fc0e102
tests: ZipFileExtensions async
carlossanlop Apr 16, 2025
4f0a6b0
tests: The rest of the tests adapted to async
carlossanlop Apr 16, 2025
f285d1e
Remove unnecessary GC.SuppressFinalize calls in Dispose methods.
carlossanlop Apr 16, 2025
f84bf1d
tests: Add tests that compare output of sync and async.
carlossanlop Apr 16, 2025
5e65586
src: Add missed overloaded method in ZipFileExtensions.
carlossanlop Apr 16, 2025
093238e
src: Share some more code in ZipFileExtensions, add missing docs
carlossanlop Apr 16, 2025
2148fd5
src: Increase bufferSize in ZipFileExtensions FileStream to 0x4000 (1…
carlossanlop Apr 16, 2025
4eefc87
src: Avoid nullable archiveStream in new constructor.
carlossanlop Apr 16, 2025
7efc9ac
src: Remove default cancellation token in private and internal method…
carlossanlop Apr 16, 2025
722de82
src: DceideArchiveStream should throw expected exception if stream is…
carlossanlop Apr 16, 2025
7a9f252
src: Simplify GetFileStreamForOpen switch.
carlossanlop Apr 16, 2025
82dbf3b
src: Share the file stream buffer size. Move the filestream comment t…
carlossanlop Apr 16, 2025
59484c0
src: ZipBlocks put together two checks that return false.
carlossanlop Apr 16, 2025
c629fa3
src: Simplify return values in async code and shared code.
carlossanlop Apr 16, 2025
fda1978
Fuzz test for sync and async
carlossanlop Apr 16, 2025
f3566a6
tests: Add special tests that confirm that the new async APIs are not…
carlossanlop Apr 16, 2025
d4509c5
src: Fix bugs found with the tests that check if async APIs are not c…
carlossanlop Apr 16, 2025
0ad6c39
src: Fix test method rename build failure
carlossanlop Apr 16, 2025
a87d789
tests: Remove two of the four outerloop tests as they're too heavy fo…
carlossanlop Apr 17, 2025
6afdb55
src: Implement the missing async methods in internal helper stream cl…
carlossanlop Apr 17, 2025
0b6e6fe
src: Fix NullReferenceException and static initialization issues in s…
carlossanlop Apr 17, 2025
7458a57
tests: revert zipfile unix tests as they use remote executor and when…
carlossanlop Apr 17, 2025
cb1e358
tests: Split the ExtractOutOfRoot tests into sync and async instead o…
carlossanlop Apr 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions eng/pipelines/libraries/fuzzing/deploy-to-onefuzz.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,12 @@ extends:
onefuzzDropDirectory: $(fuzzerProject)/deployment/Utf8JsonWriterFuzzer
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
displayName: Send Utf8JsonWriterFuzzer to OneFuzz

- task: onefuzz-task@0
inputs:
onefuzzOSes: 'Windows'
env:
onefuzzDropDirectory: $(fuzzerProject)/deployment/ZipArchiveFuzzer
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
displayName: Send ZipArchiveFuzzer to OneFuzz
# ONEFUZZ_TASK_WORKAROUND_END
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public async Task FlushAsync_DuringFlushAsync()
{
byte[] buffer = null;
string testFilePath = CompressedTestFile(UncompressedTestFile());
using (var origStream = await LocalMemoryStream.readAppFileAsync(testFilePath))
using (var origStream = await LocalMemoryStream.ReadAppFileAsync(testFilePath))
{
buffer = origStream.ToArray();
}
Expand Down Expand Up @@ -164,8 +164,8 @@ public virtual async Task Dispose_WithUnfinishedReadAsync()
[MemberData(nameof(UncompressedTestFiles))]
public async Task Read(string testFile)
{
var uncompressedStream = await LocalMemoryStream.readAppFileAsync(testFile);
var compressedStream = await LocalMemoryStream.readAppFileAsync(CompressedTestFile(testFile));
var uncompressedStream = await LocalMemoryStream.ReadAppFileAsync(testFile);
var compressedStream = await LocalMemoryStream.ReadAppFileAsync(CompressedTestFile(testFile));
using var decompressor = CreateStream(compressedStream, CompressionMode.Decompress);
var decompressorOutput = new MemoryStream();

Expand Down Expand Up @@ -199,7 +199,7 @@ public async Task Read(string testFile)
[Fact]
public async Task Read_EndOfStreamPosition()
{
var compressedStream = await LocalMemoryStream.readAppFileAsync(CompressedTestFile(UncompressedTestFile()));
var compressedStream = await LocalMemoryStream.ReadAppFileAsync(CompressedTestFile(UncompressedTestFile()));
int compressedEndPosition = (int)compressedStream.Length;
var rand = new Random(1024);
int _bufferSize = BufferSize * 2 - 568;
Expand All @@ -219,7 +219,7 @@ public async Task Read_EndOfStreamPosition()
public async Task Read_BaseStreamSlowly()
{
string testFile = UncompressedTestFile();
var uncompressedStream = await LocalMemoryStream.readAppFileAsync(testFile);
var uncompressedStream = await LocalMemoryStream.ReadAppFileAsync(testFile);
var compressedStream = new BadWrappedStream(BadWrappedStream.Mode.ReadSlowly, File.ReadAllBytes(CompressedTestFile(testFile)));
using var decompressor = CreateStream(compressedStream, CompressionMode.Decompress);
var decompressorOutput = new MemoryStream();
Expand Down Expand Up @@ -354,7 +354,7 @@ public async Task TestLeaveOpenAfterValidDecompress()
//Create the Stream
int _bufferSize = 1024;
var bytes = new byte[_bufferSize];
Stream compressedStream = await LocalMemoryStream.readAppFileAsync(CompressedTestFile(UncompressedTestFile()));
Stream compressedStream = await LocalMemoryStream.ReadAppFileAsync(CompressedTestFile(UncompressedTestFile()));
Stream decompressor = CreateStream(compressedStream, CompressionMode.Decompress, leaveOpen: false);

//Read some data and Close the stream
Expand Down Expand Up @@ -426,7 +426,7 @@ public void BaseStreamTest(CompressionMode mode)
[InlineData(CompressionMode.Decompress)]
public async Task BaseStream_Modify(CompressionMode mode)
{
using (var baseStream = await LocalMemoryStream.readAppFileAsync(CompressedTestFile(UncompressedTestFile())))
using (var baseStream = await LocalMemoryStream.ReadAppFileAsync(CompressedTestFile(UncompressedTestFile())))
using (var compressor = CreateStream(baseStream, mode))
{
int size = 1024;
Expand Down Expand Up @@ -457,7 +457,7 @@ public void BaseStream_NullAfterDisposeWithFalseLeaveOpen(CompressionMode mode)
[InlineData(CompressionMode.Decompress)]
public async Task BaseStream_ValidAfterDisposeWithTrueLeaveOpen(CompressionMode mode)
{
var ms = await LocalMemoryStream.readAppFileAsync(CompressedTestFile(UncompressedTestFile()));
var ms = await LocalMemoryStream.ReadAppFileAsync(CompressedTestFile(UncompressedTestFile()));
using var decompressor = CreateStream(ms, mode, leaveOpen: true);
var baseStream = BaseStream(decompressor);
Assert.Same(ms, baseStream);
Expand All @@ -475,7 +475,7 @@ public async Task BaseStream_ValidAfterDisposeWithTrueLeaveOpen(CompressionMode
[MemberData(nameof(UncompressedTestFilesZLib))]
public async Task CompressionLevel_SizeInOrder(string testFile)
{
using var uncompressedStream = await LocalMemoryStream.readAppFileAsync(testFile);
using var uncompressedStream = await LocalMemoryStream.ReadAppFileAsync(testFile);

async Task<long> GetLengthAsync(CompressionLevel compressionLevel)
{
Expand All @@ -501,7 +501,7 @@ async Task<long> GetLengthAsync(CompressionLevel compressionLevel)
[MemberData(nameof(UncompressedTestFilesZLib))]
public async Task ZLibCompressionOptions_SizeInOrder(string testFile)
{
using var uncompressedStream = await LocalMemoryStream.readAppFileAsync(testFile);
using var uncompressedStream = await LocalMemoryStream.ReadAppFileAsync(testFile);

async Task<long> GetLengthAsync(int compressionLevel)
{
Expand All @@ -512,7 +512,7 @@ async Task<long> GetLengthAsync(int compressionLevel)
await compressor.FlushAsync();
return mms.Length;
}

long fastestLength = await GetLengthAsync(1);
long optimalLength = await GetLengthAsync(5);
long smallestLength = await GetLengthAsync(9);
Expand All @@ -525,7 +525,7 @@ async Task<long> GetLengthAsync(int compressionLevel)
[MemberData(nameof(ZLibOptionsRoundTripTestData))]
public async Task RoundTripWithZLibCompressionOptions(string testFile, ZLibCompressionOptions options)
{
using var uncompressedStream = await LocalMemoryStream.readAppFileAsync(testFile);
using var uncompressedStream = await LocalMemoryStream.ReadAppFileAsync(testFile);
var compressedStream = await CompressTestFile(uncompressedStream, options);
using var decompressor = CreateStream(compressedStream, mode: CompressionMode.Decompress);
using var decompressorOutput = new MemoryStream();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public LocalMemoryStream Clone()
return local;
}

public static async Task<LocalMemoryStream> readAppFileAsync(string testFile)
public static async Task<LocalMemoryStream> ReadAppFileAsync(string testFile)
{
var baseStream = await StreamHelpers.CreateTempCopyStream(testFile);
var ms = new LocalMemoryStream();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using System.Threading.Tasks;

namespace System.IO.Compression.Tests;

// A stream meant to be used for testing that an implementation's sync methods do not accidentally call any async methods.
internal sealed class NoAsyncCallsStream : Stream
{
private readonly Stream _s;

public NoAsyncCallsStream(Stream stream) => _s = stream;

// Allows temporarily disabling the current stream's sync API usage restriction.
public bool IsRestrictionEnabled { get; set; }

public override bool CanRead => _s.CanRead;
public override bool CanSeek => _s.CanSeek;
public override bool CanTimeout => _s.CanTimeout;
public override bool CanWrite => _s.CanWrite;
public override long Length => _s.Length;
public override long Position { get => _s.Position; set => _s.Position = value; }
public override int ReadTimeout { get => _s.ReadTimeout; set => _s.ReadTimeout = value; }
public override int WriteTimeout { get => _s.WriteTimeout; set => _s.WriteTimeout = value; }
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _s.BeginRead(buffer, offset, count, callback, state);
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _s.BeginWrite(buffer, offset, count, callback, state);
public override void Close() => _s.Close();
public override int EndRead(IAsyncResult asyncResult) => _s.EndRead(asyncResult);
public override void EndWrite(IAsyncResult asyncResult) => _s.EndWrite(asyncResult);
public override bool Equals(object? obj) => _s.Equals(obj);
public override int GetHashCode() => _s.GetHashCode();
public override int ReadByte() => _s.ReadByte();
public override long Seek(long offset, SeekOrigin origin) => _s.Seek(offset, origin);
public override void SetLength(long value) => _s.SetLength(value);
public override string? ToString() => _s.ToString();

// Sync
public override void CopyTo(Stream destination, int bufferSize) => _s.CopyTo(destination, bufferSize);
protected override void Dispose(bool disposing) => _s.Dispose();
public override void Flush() => _s.Flush();
public override int Read(byte[] buffer, int offset, int count) => _s.Read(buffer, offset, count);
public override int Read(Span<byte> buffer) => _s.Read(buffer);
public override void Write(byte[] buffer, int offset, int count) => _s.Write(buffer, offset, count);
public override void Write(ReadOnlySpan<byte> buffer) => _s.Write(buffer);
public override void WriteByte(byte value) => _s.WriteByte(value);

// Async
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.CopyToAsync(destination, bufferSize, cancellationToken);
public override ValueTask DisposeAsync() =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.DisposeAsync();
public override Task FlushAsync(CancellationToken cancellationToken) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.FlushAsync();
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.ReadAsync(buffer, offset, count, cancellationToken);
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.ReadAsync(buffer, cancellationToken);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.WriteAsync(buffer, offset, count, cancellationToken);
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.WriteAsync(buffer, cancellationToken);
}
132 changes: 132 additions & 0 deletions src/libraries/Common/tests/System/IO/Compression/NoSyncCallsStream.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Threading;
using System.Threading.Tasks;

namespace System.IO.Compression.Tests;

// A stream meant to be used for testing that an implementation's async methods do not accidentally call any sync methods.
internal sealed class NoSyncCallsStream : Stream
{
private readonly Stream _s;

public NoSyncCallsStream(Stream stream) => _s = stream;

// Allows temporarily disabling the current stream's sync API usage restriction.
public bool IsRestrictionEnabled { get; set; }

public override bool CanRead => _s.CanRead;
public override bool CanSeek => _s.CanSeek;
public override bool CanTimeout => _s.CanTimeout;
public override bool CanWrite => _s.CanWrite;
public override long Length => _s.Length;
public override long Position { get => _s.Position; set => _s.Position = value; }
public override int ReadTimeout { get => _s.ReadTimeout; set => _s.ReadTimeout = value; }
public override int WriteTimeout { get => _s.WriteTimeout; set => _s.WriteTimeout = value; }
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _s.BeginRead(buffer, offset, count, callback, state);
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _s.BeginWrite(buffer, offset, count, callback, state);
public override void Close() => _s.Close();
public override int EndRead(IAsyncResult asyncResult) => _s.EndRead(asyncResult);
public override void EndWrite(IAsyncResult asyncResult) => _s.EndWrite(asyncResult);
public override bool Equals(object? obj) => _s.Equals(obj);
public override int GetHashCode() => _s.GetHashCode();
public override int ReadByte() => _s.ReadByte();
public override long Seek(long offset, SeekOrigin origin) => _s.Seek(offset, origin);
public override void SetLength(long value) => _s.SetLength(value);
public override string? ToString() => _s.ToString();

// Sync
public override void CopyTo(Stream destination, int bufferSize)
{
if (IsRestrictionEnabled)
{
throw new InvalidOperationException();
}
else
{
_s.CopyTo(destination, bufferSize);
}
}
protected override void Dispose(bool disposing)
{
// _disposing = true;
if (IsRestrictionEnabled)
{
throw new InvalidOperationException();
}
else
{
_s.Dispose();
}
}
public override void Flush()
{
if (IsRestrictionEnabled)
{
throw new InvalidOperationException();
}
else
{
_s.Flush();
}
}
public override int Read(byte[] buffer, int offset, int count) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.Read(buffer, offset, count);
public override int Read(Span<byte> buffer) =>
IsRestrictionEnabled ? throw new InvalidOperationException() : _s.Read(buffer);
public override void Write(byte[] buffer, int offset, int count)
{
bool isDeflateStream = false;

// Get the stack trace to determine the calling method
var stackTrace = new System.Diagnostics.StackTrace();
var callingMethod = stackTrace.GetFrame(1)?.GetMethod();

// Check if the calling method belongs to the DeflateStream class
if (callingMethod?.DeclaringType == typeof(System.IO.Compression.DeflateStream))
{
isDeflateStream = true;
}

if (!isDeflateStream && IsRestrictionEnabled)
{
throw new InvalidOperationException($"Parent class is {callingMethod.DeclaringType}");
}
else
{
_s.Write(buffer, offset, count);
}
}
public override void Write(ReadOnlySpan<byte> buffer)
{
if (IsRestrictionEnabled)
{
throw new InvalidOperationException();
}
else
{
_s.Write(buffer);
}
}
public override void WriteByte(byte value)
{
if (IsRestrictionEnabled)
{
throw new InvalidOperationException();
}
else
{
_s.WriteByte(value);
}
}

// Async
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => _s.CopyToAsync(destination, bufferSize, cancellationToken);
public override ValueTask DisposeAsync() => _s.DisposeAsync();
public override Task FlushAsync(CancellationToken cancellationToken) => _s.FlushAsync(cancellationToken);
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _s.ReadAsync(buffer, offset, count, cancellationToken);
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) => _s.ReadAsync(buffer, cancellationToken);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _s.WriteAsync(buffer, offset, count, cancellationToken);
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) => _s.WriteAsync(buffer, cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public static partial class StreamHelpers
{
public static async Task<MemoryStream> CreateTempCopyStream(string path)
{
var bytes = File.ReadAllBytes(path);
var bytes = await File.ReadAllBytesAsync(path);

var ms = new MemoryStream();
await ms.WriteAsync(bytes, 0, bytes.Length);
Expand Down
Loading
Loading