Runtime: Add async dispose support to ZipArchive

Created on 26 Oct 2019  路  5Comments  路  Source: dotnet/runtime

In my application I am creating a ZipArchive that writes directly to the ASP.Net Core body stream, like that:

```C#
using var zipArchive = new ZipArchive(Response.Body, ZipArchiveMode.Create, true);

Asynchronously writing file contents to that archive works fine, but as soon as the writing is finished and the `ZipArchive` gets disposed, the `ZipArchive` tries to synchronously write some more data to the stream. This results in an exception, because Kestrel, by default, doesn't allow synchronous writes anymore:

```C#
System.InvalidOperationException: Synchronous operations are disallowed. Call WriteAsync or set AllowSynchronousIO to true instead.
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpResponseStream.Write(Byte[] buffer, Int32 offset, Int32 count)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionBody.Write(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.Compression.PositionPreservingWriteOnlyStreamWrapper.Write(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.BinaryWriter.Write(UInt32 value)
   at System.IO.Compression.ZipArchiveEntry.WriteCentralDirectoryFileHeader()
   at System.IO.Compression.ZipArchive.WriteFile()
   at System.IO.Compression.ZipArchive.Dispose(Boolean disposing)
   at System.IO.Compression.ZipArchive.Dispose()

Enabling async disposal for this class and then doing the necessary writes asynchronously would probably resolve this issue.

area-System.IO.Compression

Most helpful comment

Test running successfully with https://github.com/manandre/runtime/pull/1 馃憤

[Fact]
public static async Task DiposeAsyncCallsWriteAsyncOnly()
{
    MemoryStream ms = new MemoryStream();
    CallTrackingStream trackingStream = new CallTrackingStream(ms);

    await using (ZipArchive archive = new ZipArchive(trackingStream, ZipArchiveMode.Create))
    {
        archive.CreateEntry("hey");
    }

    Assert.Equal(0, trackingStream.TimesCalled("Write"));
    Assert.NotEqual(0, trackingStream.TimesCalled("WriteAsync"));
}

All 5 comments

Does it work if you use FlushAsync before disposing? There is no flush API...

Triage:
@vtchalkov the ZipFile issue is not a duplicate.
This looks like a legitimate scenario. Next step would be to add a PR to implement it and add DisposeAsync.

This needs first to add async methods to BinaryWriter via dotnet/corefx#8382

Test running successfully with https://github.com/manandre/runtime/pull/1 馃憤

[Fact]
public static async Task DiposeAsyncCallsWriteAsyncOnly()
{
    MemoryStream ms = new MemoryStream();
    CallTrackingStream trackingStream = new CallTrackingStream(ms);

    await using (ZipArchive archive = new ZipArchive(trackingStream, ZipArchiveMode.Create))
    {
        archive.CreateEntry("hey");
    }

    Assert.Equal(0, trackingStream.TimesCalled("Write"));
    Assert.NotEqual(0, trackingStream.TimesCalled("WriteAsync"));
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

omariom picture omariom  路  3Comments

Timovzl picture Timovzl  路  3Comments

chunseoklee picture chunseoklee  路  3Comments

nalywa picture nalywa  路  3Comments

v0l picture v0l  路  3Comments