-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
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
runtime/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipCustomStreams.cs
Lines 206 to 219 in 04c4fe6
| 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