cleaned up PooledMemoryStream.cs

This commit is contained in:
Adam Hathcock
2026-04-04 11:10:43 +01:00
parent a26198a6b0
commit 2bf007542a
2 changed files with 49 additions and 159 deletions

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
namespace SharpCompress.IO;
@@ -12,8 +13,6 @@ namespace SharpCompress.IO;
/// </summary>
public sealed class PooledMemoryStream : MemoryStream
{
public const int DefaultBlockSize = 81920;
private const int MaxStreamLength = int.MaxValue;
private enum StorageMode
@@ -33,11 +32,6 @@ public sealed class PooledMemoryStream : MemoryStream
private bool _contiguousBufferExposed;
private bool _isOpen;
private bool _writable;
private bool _expandable;
private bool _exposable;
private int _origin;
private int _position;
private int _length;
private int _capacity;
@@ -47,7 +41,7 @@ public sealed class PooledMemoryStream : MemoryStream
: this(0) { }
public PooledMemoryStream(int capacity)
: this(capacity, DefaultBlockSize, ArrayPool<byte>.Shared) { }
: this(capacity, Constants.BufferSize, ArrayPool<byte>.Shared) { }
public PooledMemoryStream(int capacity, int blockSize)
: this(capacity, blockSize, ArrayPool<byte>.Shared) { }
@@ -64,10 +58,6 @@ public sealed class PooledMemoryStream : MemoryStream
_mode = StorageMode.Segmented;
_blocks = new List<byte[]>();
_isOpen = true;
_writable = true;
_expandable = true;
_exposable = true;
_origin = 0;
_position = 0;
_length = 0;
_capacity = capacity;
@@ -75,67 +65,18 @@ public sealed class PooledMemoryStream : MemoryStream
EnsureSegmentedAllocated(capacity);
}
public PooledMemoryStream(byte[] buffer)
: this(buffer, writable: true) { }
public PooledMemoryStream(byte[] buffer, bool writable)
: this(buffer, 0, buffer?.Length ?? 0, writable, publiclyVisible: false) { }
public PooledMemoryStream(byte[] buffer, int index, int count)
: this(buffer, index, count, writable: true, publiclyVisible: false) { }
public PooledMemoryStream(byte[] buffer, int index, int count, bool writable)
: this(buffer, index, count, writable, publiclyVisible: false) { }
public PooledMemoryStream(
byte[] buffer,
int index,
int count,
bool writable,
bool publiclyVisible
)
{
ThrowHelper.ThrowIfNull(buffer, nameof(buffer));
ThrowHelper.ThrowIfNegative(index, nameof(index));
ThrowHelper.ThrowIfNegative(count, nameof(count));
if (buffer.Length - index < count)
{
throw new ArgumentException("Offset and length are out of bounds.");
}
_arrayPool = ArrayPool<byte>.Shared;
_blockSize = DefaultBlockSize;
_mode = StorageMode.Segmented;
_blocks = new List<byte[]>();
_origin = 0;
_position = 0;
_length = count;
_capacity = count;
_writable = writable;
_expandable = false;
_exposable = publiclyVisible;
_isOpen = true;
EnsureSegmentedAllocated(_capacity);
if (count > 0)
{
CopyToSegmented(0, buffer, index, count);
}
}
public override bool CanRead => _isOpen;
public override bool CanSeek => _isOpen;
public override bool CanWrite => _writable;
public override bool CanWrite => _isOpen;
public override long Length
{
get
{
EnsureNotClosed();
return _length - _origin;
return _length;
}
}
@@ -144,15 +85,15 @@ public sealed class PooledMemoryStream : MemoryStream
get
{
EnsureNotClosed();
return _position - _origin;
return _position;
}
set
{
ThrowHelper.ThrowIfNegative(value, nameof(value));
EnsureNotClosed();
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength - _origin, nameof(value));
ThrowHelper.ThrowIfGreaterThan(value, MaxStreamLength, nameof(value));
_position = _origin + (int)value;
_position = (int)value;
}
}
@@ -161,23 +102,15 @@ public sealed class PooledMemoryStream : MemoryStream
get
{
EnsureNotClosed();
return _capacity - _origin;
return _capacity;
}
set
{
if (value < Length)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
ThrowHelper.ThrowIfLessThan(value, _length, nameof(value));
EnsureNotClosed();
if (!_expandable && value != Capacity)
{
throw new NotSupportedException("Memory stream is not expandable.");
}
var target = _origin + value;
var target = value;
if (target == _capacity)
{
return;
@@ -205,14 +138,14 @@ public sealed class PooledMemoryStream : MemoryStream
var anchor = loc switch
{
SeekOrigin.Begin => _origin,
SeekOrigin.Begin => 0,
SeekOrigin.Current => _position,
SeekOrigin.End => _length,
_ => throw new ArgumentException("Invalid seek origin.", nameof(loc)),
};
var target = anchor + offset;
if (target < _origin)
if (target < 0)
{
throw new IOException("Attempted to seek before the beginning of the stream.");
}
@@ -223,7 +156,7 @@ public sealed class PooledMemoryStream : MemoryStream
}
_position = (int)target;
return _position - _origin;
return _position;
}
public override void SetLength(long value)
@@ -233,7 +166,7 @@ public sealed class PooledMemoryStream : MemoryStream
EnsureWritable();
var newLength = _origin + (int)value;
var newLength = (int)value;
if (newLength > _capacity)
{
EnsureCapacityForAppend(newLength);
@@ -395,11 +328,6 @@ public sealed class PooledMemoryStream : MemoryStream
public override byte[] GetBuffer()
{
EnsureNotClosed();
if (!_exposable)
{
throw new UnauthorizedAccessException("Memory stream buffer is not publicly visible.");
}
EnsureContiguous();
_contiguousBufferExposed = true;
return _contiguousBuffer!;
@@ -407,12 +335,6 @@ public sealed class PooledMemoryStream : MemoryStream
public override bool TryGetBuffer(out ArraySegment<byte> buffer)
{
if (!_exposable)
{
buffer = default;
return false;
}
EnsureNotClosed();
EnsureContiguous();
@@ -425,7 +347,7 @@ public sealed class PooledMemoryStream : MemoryStream
{
EnsureNotClosed();
var count = _length - _origin;
var count = _length;
if (count == 0)
{
return Array.Empty<byte>();
@@ -435,10 +357,10 @@ public sealed class PooledMemoryStream : MemoryStream
switch (_mode)
{
case StorageMode.Contiguous:
Buffer.BlockCopy(_contiguousBuffer!, _origin, copy, 0, count);
Buffer.BlockCopy(_contiguousBuffer!, 0, copy, 0, count);
break;
case StorageMode.Segmented:
CopyFromSegmented(_origin, copy, 0, count);
CopyFromSegmented(0, copy, 0, count);
break;
}
@@ -450,7 +372,7 @@ public sealed class PooledMemoryStream : MemoryStream
ThrowHelper.ThrowIfNull(stream, nameof(stream));
EnsureNotClosed();
var count = _length - _origin;
var count = _length;
if (count == 0)
{
return;
@@ -459,11 +381,11 @@ public sealed class PooledMemoryStream : MemoryStream
switch (_mode)
{
case StorageMode.Contiguous:
stream.Write(_contiguousBuffer!, _origin, count);
stream.Write(_contiguousBuffer!, 0, count);
break;
case StorageMode.Segmented:
{
var position = _origin;
var position = 0;
var remaining = count;
while (remaining > 0)
{
@@ -526,7 +448,7 @@ public sealed class PooledMemoryStream : MemoryStream
}
#if !LEGACY_DOTNET
public override int Read(Span<byte> buffer)
public override int Read(Span<byte> destination)
{
EnsureNotClosed();
@@ -536,11 +458,11 @@ public sealed class PooledMemoryStream : MemoryStream
return 0;
}
var count = Math.Min(available, buffer.Length);
var count = Math.Min(available, destination.Length);
switch (_mode)
{
case StorageMode.Contiguous:
_contiguousBuffer.AsSpan(_position, count).CopyTo(buffer);
_contiguousBuffer.AsSpan(_position, count).CopyTo(destination);
break;
case StorageMode.Segmented:
{
@@ -556,7 +478,7 @@ public sealed class PooledMemoryStream : MemoryStream
_blocks!
[blockIndex]
.AsSpan(blockOffset, toCopy)
.CopyTo(buffer.Slice(destinationOffset, toCopy));
.CopyTo(destination.Slice(destinationOffset, toCopy));
sourcePosition += toCopy;
destinationOffset += toCopy;
@@ -571,15 +493,15 @@ public sealed class PooledMemoryStream : MemoryStream
return count;
}
public override void Write(ReadOnlySpan<byte> buffer)
public override void Write(ReadOnlySpan<byte> source)
{
EnsureWritable();
if (buffer.Length == 0)
if (source.Length == 0)
{
return;
}
var endPosition = _position + buffer.Length;
var endPosition = _position + source.Length;
if (endPosition < 0)
{
throw new IOException("Stream is too long.");
@@ -598,13 +520,13 @@ public sealed class PooledMemoryStream : MemoryStream
switch (_mode)
{
case StorageMode.Contiguous:
buffer.CopyTo(_contiguousBuffer.AsSpan(_position, buffer.Length));
source.CopyTo(_contiguousBuffer.AsSpan(_position, source.Length));
break;
case StorageMode.Segmented:
{
var sourceOffset = 0;
var destinationPosition = _position;
var remaining = buffer.Length;
var remaining = source.Length;
while (remaining > 0)
{
@@ -612,7 +534,7 @@ public sealed class PooledMemoryStream : MemoryStream
var blockOffset = destinationPosition % _blockSize;
var toCopy = Math.Min(remaining, _blockSize - blockOffset);
buffer
source
.Slice(sourceOffset, toCopy)
.CopyTo(_blocks![blockIndex].AsSpan(blockOffset, toCopy));
@@ -633,7 +555,7 @@ public sealed class PooledMemoryStream : MemoryStream
}
public override ValueTask<int> ReadAsync(
Memory<byte> buffer,
Memory<byte> destination,
CancellationToken cancellationToken = default
)
{
@@ -644,7 +566,7 @@ public sealed class PooledMemoryStream : MemoryStream
try
{
return ValueTask.FromResult(Read(buffer.Span));
return ValueTask.FromResult(Read(destination.Span));
}
catch (Exception ex)
{
@@ -653,7 +575,7 @@ public sealed class PooledMemoryStream : MemoryStream
}
public override ValueTask WriteAsync(
ReadOnlyMemory<byte> buffer,
ReadOnlyMemory<byte> source,
CancellationToken cancellationToken = default
)
{
@@ -664,7 +586,7 @@ public sealed class PooledMemoryStream : MemoryStream
try
{
Write(buffer.Span);
Write(source.Span);
return ValueTask.CompletedTask;
}
catch (Exception ex)
@@ -679,8 +601,6 @@ public sealed class PooledMemoryStream : MemoryStream
if (_isOpen)
{
_isOpen = false;
_writable = false;
_expandable = false;
if (disposing)
{
@@ -702,10 +622,6 @@ public sealed class PooledMemoryStream : MemoryStream
private void EnsureWritable()
{
EnsureNotClosed();
if (!_writable)
{
throw new NotSupportedException("Stream does not support writing.");
}
}
private void EnsureCapacityForAppend(int requiredLength)
@@ -720,11 +636,6 @@ public sealed class PooledMemoryStream : MemoryStream
return;
}
if (!_expandable)
{
throw new NotSupportedException("Memory stream is not expandable.");
}
var nextCapacity = RoundUpToBlockBoundary(requiredLength);
SetCapacityAbsolute(nextCapacity);
}

View File

@@ -54,49 +54,28 @@ public class PooledMemoryStreamTests
}
[Fact]
public void BufferConstructorCopiesDataAndIsNonExpandable()
public void TryGetBufferReturnsSegmentWhenOpen()
{
var backing = Enumerable.Range(0, 10).Select(i => (byte)i).ToArray();
using var stream = new PooledMemoryStream(
backing,
2,
4,
writable: true,
publiclyVisible: true
);
using var stream = new PooledMemoryStream(capacity: 0, blockSize: 8);
stream.Write(new byte[] { 1, 2, 3, 4 }, 0, 4);
Assert.Equal(4, stream.Length);
Assert.Equal(4, stream.Capacity);
backing[2] = 255;
Assert.Equal(new byte[] { 2, 3, 4, 5 }, stream.ToArray());
Assert.Throws<NotSupportedException>(() => stream.Capacity = 5);
Assert.True(stream.TryGetBuffer(out var segment));
Assert.Equal(0, segment.Offset);
Assert.Equal(4, segment.Count);
Assert.Equal(1, segment.Array![0]);
}
[Fact]
public void BufferConstructorTryGetBufferRespectsVisibilityFlag()
public void CapacitySetterCanGrowAndShrinkWithinLength()
{
var backing = new byte[10];
using var hidden = new PooledMemoryStream(
backing,
1,
5,
writable: true,
publiclyVisible: false
);
Assert.False(hidden.TryGetBuffer(out _));
using var stream = new PooledMemoryStream(capacity: 16, blockSize: 8);
stream.Write(new byte[6], 0, 6);
using var visible = new PooledMemoryStream(
backing,
1,
5,
writable: true,
publiclyVisible: true
);
Assert.True(visible.TryGetBuffer(out var segment));
Assert.Equal(0, segment.Offset);
Assert.Equal(5, segment.Count);
stream.Capacity = 24;
Assert.Equal(24, stream.Capacity);
stream.Capacity = 8;
Assert.Equal(8, stream.Capacity);
}
[Fact]