Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Asynchronous Zip Methods still have synchronous callsΒ #121624

@Tornhoof

Description

@Tornhoof

Description

In #114421 async Zip methods were added, as e.g., Kestrel does not like synchronous writes.
This still does not work 100%. See below repro:

System.NotImplementedException
  HResult=0x80004001
  Message=The method or operation is not implemented.
  Source=SyncZipFlush
  StackTrace:
   at SyncZipFlush.AsyncOnlyStream.Write(Byte[] buffer, Int32 offset, Int32 count) in D:\Repros\SyncZipFlush\SyncZipFlush\Program.cs:line 55
   at System.IO.Compression.DeflateStream.PurgeBuffers(Boolean disposing)
   at System.IO.Compression.DeflateStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Compression.CheckSumAndSizeWriteStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Compression.ZipArchiveEntry.DirectToArchiveWriterStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Compression.WrappedStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at System.IO.Stream.DisposeAsync()

The internal WrapperStream

protected override void Dispose(bool disposing)
{
if (disposing && !_isDisposed)
{
_onClosed?.Invoke(_zipArchiveEntry);
if (_closeBaseStream)
_baseStream.Dispose();
_isDisposed = true;
}
base.Dispose(disposing);
}
}

does not implement DisposeAsync, thus the sync Dispose method is called making all the following dispose calls sync and since there is still work to be done on dispose, the sync write call is invoked and fails.

Reproduction Steps

using System.IO.Compression;
using System.Text;

namespace SyncZipFlush
{

    internal class Program
    {
        public static async Task Main(string[] args)
        {
            var asyncOnlyStream = new AsyncOnlyStream(new MemoryStream());
            CancellationToken cancellationToken = CancellationToken.None;
            var tempFile = Path.GetTempFileName();
            var bytes = new byte[1024];
            Random.Shared.NextBytes(bytes);
            await File.WriteAllBytesAsync(tempFile, bytes, cancellationToken).ConfigureAwait(false);
            var inputStream = File.OpenRead(tempFile);
            await using (var zipArchive = new ZipArchive(asyncOnlyStream, ZipArchiveMode.Create, leaveOpen: true, Encoding.UTF8))
            {
                var entry = zipArchive.CreateEntry("Test");
                await using var s = inputStream;
                await using var outputStream = await entry.OpenAsync(cancellationToken).ConfigureAwait(false);
                await s.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
                await outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
            }
        }
    }

    public sealed class AsyncOnlyStream : Stream
    {
        private readonly MemoryStream _innerStream;

        public AsyncOnlyStream(MemoryStream innerStream)
        {
            _innerStream = innerStream;
        }

        public override void Flush()
        {
            throw new NotImplementedException();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            return _innerStream.Seek(offset, origin);
        }

        public override void SetLength(long value)
        {
            _innerStream.SetLength(value);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
        {
            return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken);
        }

        public override Task FlushAsync(CancellationToken cancellationToken)
        {
            return _innerStream.FlushAsync(cancellationToken);
        }

        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
        {
            return _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
        }

        public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
        {
            return _innerStream.WriteAsync(buffer, cancellationToken);
        }

        public override ValueTask DisposeAsync()
        {
            return _innerStream.DisposeAsync();
        }

        public override bool CanRead => _innerStream.CanRead;

        public override bool CanSeek => _innerStream.CanSeek;

        public override bool CanWrite => _innerStream.CanWrite;

        public override long Length => _innerStream.Length;

        public override long Position
        {
            get => _innerStream.Position;
            set => _innerStream.Position = value;
        }
    }
}

Expected behavior

No sync calls.

Actual behavior

Sync calls.

Regression?

No regression. Async Zip Support is new in .NET 10.

Known Workarounds

Wrap the output in some kind of wrapper stream which prevents the sync calls (via caching or similar).

Configuration

.NET 10 rtm
Host:
Version: 10.0.0
Architecture: x64
Commit: b0f34d51fc

Other information

No response

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions