Make raw LZMA setup and disposal exception-safe

This commit is contained in:
Adam Hathcock
2026-10-13 10:29:07 +01:00
parent 38e3222dc2
commit bdb42df4dc
8 changed files with 291 additions and 75 deletions

View File

@@ -24,13 +24,20 @@ internal partial class OutWindow : IAsyncDisposable
public async ValueTask DisposeAsync()
{
await ReleaseStreamAsync().ConfigureAwait(false);
if (_buffer is null)
try
{
return;
await ReleaseStreamAsync().ConfigureAwait(false);
}
finally
{
var buffer = _buffer;
_buffer = null;
_stream = null;
if (buffer is not null)
{
_pool.Return(buffer);
}
}
_pool.Return(_buffer);
_buffer = null;
}
private async ValueTask FlushAsync(CancellationToken cancellationToken = default)

View File

@@ -58,13 +58,20 @@ internal partial class OutWindow : IDisposable
public void Dispose()
{
ReleaseStream();
if (_buffer is null)
try
{
return;
ReleaseStream();
}
finally
{
var buffer = _buffer;
_buffer = null;
_stream = null;
if (buffer is not null)
{
_pool.Return(buffer);
}
}
_pool.Return(_buffer);
_buffer = null;
}
public void Reset()

View File

@@ -354,7 +354,7 @@ internal sealed class Lzma2EncoderStream : Stream
props[3] = (byte)(_dictionarySize >> 16);
props[4] = (byte)(_dictionarySize >> 24);
var decoder = new Decoder();
using var decoder = new Decoder();
decoder.SetDecoderProperties(props);
using var input = new MemoryStream(compressedData, 0, compressedSize, writable: false);

View File

@@ -43,24 +43,32 @@ public partial class LzmaStream
isLzma2,
leaveOpen
);
if (!isLzma2)
try
{
if (presetDictionary != null)
if (!isLzma2)
{
await lzma._outWindow.TrainAsync(presetDictionary).ConfigureAwait(false);
}
if (presetDictionary != null)
{
await lzma._outWindow.TrainAsync(presetDictionary).ConfigureAwait(false);
}
await lzma._rangeDecoder.InitAsync(inputStream).ConfigureAwait(false);
}
else
{
if (presetDictionary != null)
{
await lzma._outWindow.TrainAsync(presetDictionary).ConfigureAwait(false);
lzma._needDictReset = false;
await lzma._rangeDecoder.InitAsync(inputStream).ConfigureAwait(false);
}
else
{
if (presetDictionary != null)
{
await lzma._outWindow.TrainAsync(presetDictionary).ConfigureAwait(false);
lzma._needDictReset = false;
}
}
return lzma;
}
catch
{
await lzma.ReleaseBuffersAsync().ConfigureAwait(false);
throw;
}
return lzma;
}
/*public static async ValueTask<LzmaStream> CreateAsync(
@@ -175,6 +183,7 @@ public partial class LzmaStream
CancellationToken cancellationToken
)
{
ThrowIfDisposed();
if (_endReached)
{
return 0;
@@ -289,6 +298,7 @@ public partial class LzmaStream
CancellationToken cancellationToken = default
)
{
ThrowIfDisposed();
if (_endReached)
{
return 0;
@@ -407,6 +417,7 @@ public partial class LzmaStream
CancellationToken cancellationToken
)
{
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
if (_encoder != null)
{
@@ -422,6 +433,7 @@ public partial class LzmaStream
CancellationToken cancellationToken = default
)
{
ThrowIfDisposed();
cancellationToken.ThrowIfCancellationRequested();
if (_encoder != null)
{
@@ -443,30 +455,59 @@ public partial class LzmaStream
return;
}
_isDisposed = true;
if (_encoder != null)
try
{
_position = await _encoder.CodeAsync(null, true).ConfigureAwait(false);
_encoder.Dispose();
}
if (!_leaveOpen)
{
if (_inputStream is IAsyncDisposable asyncDisposableInputStream)
if (_encoder != null)
{
await asyncDisposableInputStream.DisposeAsync().ConfigureAwait(false);
}
else
{
_inputStream?.Dispose();
_position = await _encoder.CodeAsync(null, true).ConfigureAwait(false);
}
}
finally
{
try
{
await ReleaseBuffersAsync().ConfigureAwait(false);
}
finally
{
if (!_leaveOpen)
{
if (_inputStream is IAsyncDisposable asyncDisposableInputStream)
{
await asyncDisposableInputStream.DisposeAsync().ConfigureAwait(false);
}
else
{
_inputStream?.Dispose();
}
}
}
}
await _outWindow.DisposeAsync().ConfigureAwait(false);
ReturnAsyncHeaderBuffer();
#if !LEGACY_DOTNET || NETSTANDARD2_1
await base.DisposeAsync().ConfigureAwait(false);
#endif
GC.SuppressFinalize(this);
}
private async ValueTask ReleaseBuffersAsync()
{
try
{
#pragma warning disable VSTHRD103 // Encoder.Dispose only returns buffers; finalization is separate.
_encoder?.Dispose();
#pragma warning restore VSTHRD103
}
finally
{
try
{
await _outWindow.DisposeAsync().ConfigureAwait(false);
}
finally
{
ReturnAsyncHeaderBuffer();
}
}
}
}

View File

@@ -54,24 +54,23 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
if (!isLzma2)
{
_dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1));
_outWindow.Create(_dictionarySize);
_decoder = new Decoder();
_decoder.SetDecoderProperties(properties);
Properties = properties;
_availableBytes = outputSize < 0 ? long.MaxValue : outputSize;
_rangeDecoderLimit = inputSize;
// Validate decoder properties before acquiring the dictionary buffer.
_outWindow.Create(_dictionarySize);
}
else
{
_dictionarySize = 2 | (properties[0] & 1);
_dictionarySize <<= (properties[0] >> 1) + 11;
_outWindow.Create(_dictionarySize);
Properties = new byte[1];
_availableBytes = 0;
_outWindow.Create(_dictionarySize);
}
}
@@ -123,24 +122,32 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
isLzma2,
leaveOpen
);
if (!isLzma2)
try
{
if (presetDictionary != null)
if (!isLzma2)
{
lzma._outWindow.Train(presetDictionary);
}
if (presetDictionary != null)
{
lzma._outWindow.Train(presetDictionary);
}
lzma._rangeDecoder.Init(inputStream);
}
else
{
if (presetDictionary != null)
{
lzma._outWindow.Train(presetDictionary);
lzma._needDictReset = false;
lzma._rangeDecoder.Init(inputStream);
}
else
{
if (presetDictionary != null)
{
lzma._outWindow.Train(presetDictionary);
lzma._needDictReset = false;
}
}
return lzma;
}
catch
{
lzma.ReleaseBuffers();
throw;
}
return lzma;
}
private LzmaStream(LzmaEncoderProperties properties, bool isLzma2)
@@ -175,21 +182,28 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
)
{
var lzma = new LzmaStream(properties, isLzma2);
lzma._encoder!.SetStreams(null, outputStream, -1, -1);
if (presetDictionary != null)
try
{
lzma._encoder.Train(presetDictionary);
lzma._encoder!.SetStreams(null, outputStream, -1, -1);
if (presetDictionary != null)
{
lzma._encoder.Train(presetDictionary);
}
return lzma;
}
catch
{
lzma.ReleaseBuffers();
throw;
}
return lzma;
}
public override bool CanRead => _encoder == null;
public override bool CanRead => !_isDisposed && _encoder == null;
public override bool CanSeek => false;
public override bool CanWrite => _encoder != null;
public override bool CanWrite => !_isDisposed && _encoder != null;
public override void Flush() { }
@@ -202,21 +216,58 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
return;
}
_isDisposed = true;
if (disposing)
try
{
if (_encoder != null)
if (disposing && _encoder != null)
{
_position = _encoder.Code(null, true);
_encoder.Dispose();
}
if (!_leaveOpen)
{
_inputStream?.Dispose();
}
_outWindow.Dispose();
ReturnAsyncHeaderBuffer();
}
base.Dispose(disposing);
finally
{
if (disposing)
{
try
{
ReleaseBuffers();
}
finally
{
if (!_leaveOpen)
{
_inputStream?.Dispose();
}
}
}
base.Dispose(disposing);
}
}
private void ReleaseBuffers()
{
try
{
_encoder?.Dispose();
}
finally
{
try
{
_outWindow.Dispose();
}
finally
{
ReturnAsyncHeaderBuffer();
}
}
}
private void ThrowIfDisposed()
{
if (_isDisposed)
{
throw new ObjectDisposedException(nameof(LzmaStream));
}
}
private byte[] GetAsyncHeaderBuffer() => _asyncHeaderBuffer ??= ArrayPool<byte>.Shared.Rent(6);
@@ -242,6 +293,7 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
public override int Read(byte[] buffer, int offset, int count)
{
ThrowIfDisposed();
if (_endReached)
{
return 0;
@@ -337,6 +389,7 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
public override int ReadByte()
{
ThrowIfDisposed();
if (_endReached)
{
return -1;
@@ -506,6 +559,7 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
public override void Write(byte[] buffer, int offset, int count)
{
ThrowIfDisposed();
if (_encoder != null)
{
_position = _encoder.Code(new MemoryStream(buffer, offset, count), false);