Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 14, 2025

Fixes #121624

The async Zip methods added in #114421 still made synchronous calls during disposal, causing failures with async-only streams (e.g., Kestrel response streams). The WrappedStream class lacked a DisposeAsync override, so await using fell back to synchronous Dispose(), which then made synchronous writes.

Changes

  • Added DisposeAsync override to WrappedStream that asynchronously disposes the base stream
  • Fixed existing NoSyncCallsWhenUsingAsync test to properly validate async-only behavior:
    • Set IsRestrictionEnabled = true on NoSyncCallsStream to enforce async-only restrictions
    • Removed DeflateStream filtering from NoSyncCallsStream.Write() to properly catch all synchronous calls
    • Changed test to use CopyToAsync instead of CopyTo for proper async operation

Example

// Previously failed with NotImplementedException on sync Write()
var asyncOnlyStream = new KestrelResponseStream();
await using (var zip = new ZipArchive(asyncOnlyStream, ZipArchiveMode.Create))
{
    var entry = zip.CreateEntry("file.txt");
    await using var stream = await entry.OpenAsync();
    await stream.WriteAsync(data);
}  // DisposeAsync now properly async, no sync calls

Follows the same pattern as CheckSumAndSizeWriteStream.DisposeAsync in the same file.


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix synchronous calls in asynchronous Zip methods Add DisposeAsync to WrappedStream to prevent synchronous calls in async Zip operations Nov 14, 2025
Copilot AI requested a review from stephentoub November 14, 2025 16:11
Copilot finished work on behalf of stephentoub November 14, 2025 16:11
@stephentoub stephentoub marked this pull request as ready for review November 14, 2025 16:58
Copilot AI review requested due to automatic review settings November 14, 2025 16:58
Copilot finished reviewing on behalf of stephentoub November 14, 2025 17:00
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 PR fixes an issue where asynchronous ZIP archive operations still made synchronous calls during disposal, causing failures with async-only streams like Kestrel response streams. The problem was that WrappedStream lacked a DisposeAsync override, causing await using statements to fall back to synchronous disposal.

  • Added DisposeAsync override to WrappedStream that asynchronously disposes the base stream
  • Added comprehensive test with async-only stream wrapper to verify no synchronous calls occur during async disposal

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCustomStreams.cs Added DisposeAsync method to WrappedStream class following the same pattern as CheckSumAndSizeWriteStream.DisposeAsync
src/libraries/System.IO.Compression/tests/ZipArchive/zip_netcoreappTests.cs Added test AsyncOnlyStream_NoSynchronousCalls and helper class AsyncOnlyStream to verify async operations don't make synchronous calls

}

[Fact]
public static async Task AsyncOnlyStream_NoSynchronousCalls()

Choose a reason for hiding this comment

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

There is already a test for this, but it fails to validate that DisposeAsync has no sync calls due to a test bug

see #121624 (comment)

Copy link
Member

@rzikm rzikm Nov 24, 2025

Choose a reason for hiding this comment

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

It seems the test does not assert the async-only execution because:

s.IsRestrictionEnabled is left to the default (false), same for NoAsync* version

public static async Task NoAsyncCallsWhenUsingSync()
{
using MemoryStream ms = new();
using NoAsyncCallsStream s = new(ms); // Only allows sync calls
// Create mode

And on top of it, it explicitly ignores violations if called from DeflateStream?!

// 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}");
}

I think we can safely remove that filtering.

Fixing those two places makes the new test redundant.

Copy link
Member

Choose a reason for hiding this comment

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

@copilot Can you address my comment?

Copilot AI requested a review from rzikm November 24, 2025 11:54
Copilot finished work on behalf of rzikm November 24, 2025 11:54
@rzikm
Copy link
Member

rzikm commented Nov 24, 2025

@copilot remove the new test and fix the existing one as per instructions in my comment

Copy link
Contributor Author

Copilot AI commented Nov 24, 2025

@copilot remove the new test and fix the existing one as per instructions in my comment

I don't see the detailed instructions you're referring to in the comments provided. Could you please clarify which existing test needs to be fixed and what changes are needed? I want to make sure I address your feedback correctly.

Copilot finished work on behalf of rzikm November 24, 2025 12:40
@rzikm
Copy link
Member

rzikm commented Nov 24, 2025

@copilot, let me repeat

There is already a test for not using sync methods in async methods: zip_InvalidParametersAndStrangeFiles .NoSyncCallsWhenUsingAsync. That test needs to be fixed so that it tests the functionality properly:

Make sure IsRestrictionEnabled is set to true on NoAsyncCallsStream in the test, and remove the filtering of methods called from DeflateStream inside NoAsyncCallsStream. Then you can remove the new test that you added.

…eview feedback

Co-authored-by: rzikm <32671551+rzikm@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 24, 2025

@copilot, let me repeat

There is already a test for not using sync methods in async methods: zip_InvalidParametersAndStrangeFiles .NoSyncCallsWhenUsingAsync. That test needs to be fixed so that it ...

Done in commit 2634862. I've:

  1. Removed the DeflateStream filtering from NoSyncCallsStream.Write()
  2. Set IsRestrictionEnabled = true on NoSyncCallsStream in the test
  3. Changed the test to use CopyToAsync instead of CopyTo
  4. Removed the new test I had added

The existing test now properly validates that no synchronous calls are made when using async methods.

Copilot finished work on behalf of rzikm November 24, 2025 13:55
@ZingBallyhoo
Copy link

ZingBallyhoo commented Nov 24, 2025

Now that we know the bypass for DeflateStream was incorrect, its safe to say IsRestrictionEnabled should be completely removed for this to be considered fixed.

That would also mean fixing CreateAsync -> .Entries.

Reading via an async-only stream currently doesn't work either. CreateAsync matches sync implementation 1:1, but the API review called out that it should ensure the data is loaded before returning. As EnsureCentralDirectoryReadAsync is never called, accessing .Entries causes the directory to be read via the sync api.

I'm happy to PR this myself if Copilot can't be coerced to do it.

@rzikm
Copy link
Member

rzikm commented Nov 24, 2025

Those sound like good improvements, we would gladly accept a contribution :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Asynchronous Zip Methods still have synchronous calls

4 participants