Skip to content

Instantly share code, notes, and snippets.

@kfrancis
Created April 3, 2024 01:33
Show Gist options
  • Save kfrancis/2753222b77334ea8c37566a583b61623 to your computer and use it in GitHub Desktop.
Save kfrancis/2753222b77334ea8c37566a583b61623 to your computer and use it in GitHub Desktop.
Version of LimitArrayPoolWriteStream compat with netstandard2.0
internal sealed class LimitArrayPoolWriteStream : Stream
{
private const int InitialLength = 256;
private readonly int _maxBufferSize;
private byte[] _buffer;
private int _length;
public LimitArrayPoolWriteStream(int maxBufferSize) : this(maxBufferSize, InitialLength) { }
public LimitArrayPoolWriteStream(int maxBufferSize, long capacity)
{
if (capacity < InitialLength)
{
capacity = InitialLength;
}
else if (capacity > maxBufferSize)
{
throw CreateOverCapacityException(maxBufferSize);
}
_maxBufferSize = maxBufferSize;
_buffer = ArrayPool<byte>.Shared.Rent((int)capacity);
}
protected override void Dispose(bool disposing)
{
Debug.Assert(_buffer != null);
ArrayPool<byte>.Shared.Return(_buffer);
_buffer = null!;
base.Dispose(disposing);
}
public ArraySegment<byte> GetBuffer() => new(_buffer, 0, _length);
public byte[] ToArray()
{
var arr = new byte[_length];
Buffer.BlockCopy(_buffer, 0, arr, 0, _length);
return arr;
}
private void EnsureCapacity(int value)
{
if ((uint)value > (uint)_maxBufferSize) // value cast handles overflow to negative as well
{
throw CreateOverCapacityException(_maxBufferSize);
}
else if (value > _buffer.Length)
{
Grow(value);
}
}
private void Grow(int value)
{
Debug.Assert(value > _buffer.Length);
// Extract the current buffer to be replaced.
var currentBuffer = _buffer;
_buffer = null!;
// Determine the capacity to request for the new buffer. It should be
// at least twice as long as the current one, if not more if the requested
// value is more than that. If the new value would put it longer than the max
// allowed byte array, than shrink to that (and if the required length is actually
// longer than that, we'll let the runtime throw).
var twiceLength = 2 * (uint)currentBuffer.Length;
var newCapacity = twiceLength > int.MaxValue ?
Math.Max(value, int.MaxValue) :
Math.Max(value, (int)twiceLength);
// Get a new buffer, copy the current one to it, return the current one, and
// set the new buffer as current.
var newBuffer = ArrayPool<byte>.Shared.Rent(newCapacity);
Buffer.BlockCopy(currentBuffer, 0, newBuffer, 0, _length);
ArrayPool<byte>.Shared.Return(currentBuffer);
_buffer = newBuffer;
}
public override void Write(byte[] buffer, int offset, int count)
{
Debug.Assert(buffer != null);
Debug.Assert(offset >= 0);
Debug.Assert(count >= 0);
EnsureCapacity(_length + count);
Buffer.BlockCopy(buffer, offset, _buffer, _length, count);
_length += count;
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
Write(buffer, offset, count);
return Task.CompletedTask;
}
public override void WriteByte(byte value)
{
var newLength = _length + 1;
EnsureCapacity(newLength);
_buffer[_length] = value;
_length = newLength;
}
public override void Flush() { }
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override long Length => _length;
public override bool CanWrite => true;
public override bool CanRead => false;
public override bool CanSeek => false;
public override long Position { get { throw new NotSupportedException(); } set { throw new NotSupportedException(); } }
public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); }
public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); }
public override void SetLength(long value) { throw new NotSupportedException(); }
private static InvalidOperationException CreateOverCapacityException(int maxBufferSize)
{
return new InvalidOperationException($"Buffer size exceeded maximum of {maxBufferSize} bytes.");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment