Skip to content

Commit f2218c4

Browse files
Add PooledArrayBufferWriter<T>
PooledArrayBufferWriter<T> is an implementation of IBufferWriter<T> that uses shared array pools.
1 parent 8dc370d commit f2218c4

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Buffers;
6+
using Xunit;
7+
8+
namespace Microsoft.AspNetCore.Razor.Utilities.Shared.Test;
9+
10+
public class PooledArrayBufferWriterTests
11+
{
12+
[Theory]
13+
[InlineData(42)]
14+
[InlineData(256)]
15+
[InlineData(400)]
16+
[InlineData(1024)]
17+
[InlineData(8 * 1024)]
18+
public void FillBufferWriter(int arraySize)
19+
{
20+
// Create an array filled with byte data.
21+
Span<byte> span = new byte[arraySize];
22+
23+
for (var i = 0; i < arraySize; i++)
24+
{
25+
span[i] = (byte)(i % 255);
26+
}
27+
28+
using var bufferWriter = new PooledArrayBufferWriter<byte>();
29+
30+
bufferWriter.Write(span);
31+
32+
Assert.True(span.SequenceEqual(bufferWriter.WrittenMemory.Span));
33+
}
34+
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT license. See License.txt in the project root for license information.
3+
4+
// Copied from https://github.com/dotnet/runtime
5+
6+
using System.Diagnostics;
7+
using System.Diagnostics.CodeAnalysis;
8+
using System.Runtime.CompilerServices;
9+
using Microsoft.AspNetCore.Razor;
10+
11+
namespace System.Buffers;
12+
13+
/// <summary>
14+
/// <see cref="IBufferWriter{T}"/> that uses <see cref="ArrayPool{T}"/>.
15+
/// </summary>
16+
internal sealed class PooledArrayBufferWriter<T> : IBufferWriter<T>, IDisposable
17+
{
18+
private T[]? _rentedBuffer;
19+
private int _index;
20+
21+
private const int MinimumBufferSize = 256;
22+
23+
public PooledArrayBufferWriter()
24+
{
25+
_rentedBuffer = ArrayPool<T>.Shared.Rent(MinimumBufferSize);
26+
_index = 0;
27+
}
28+
29+
public PooledArrayBufferWriter(int initialCapacity)
30+
{
31+
ArgHelper.ThrowIfNegativeOrZero(initialCapacity);
32+
33+
_rentedBuffer = ArrayPool<T>.Shared.Rent(initialCapacity);
34+
_index = 0;
35+
}
36+
37+
public ReadOnlyMemory<T> WrittenMemory
38+
{
39+
get
40+
{
41+
CheckIfDisposed();
42+
43+
return _rentedBuffer.AsMemory(0, _index);
44+
}
45+
}
46+
47+
public int WrittenCount
48+
{
49+
get
50+
{
51+
CheckIfDisposed();
52+
53+
return _index;
54+
}
55+
}
56+
57+
public int Capacity
58+
{
59+
get
60+
{
61+
CheckIfDisposed();
62+
63+
return _rentedBuffer.Length;
64+
}
65+
}
66+
67+
public int FreeCapacity
68+
{
69+
get
70+
{
71+
CheckIfDisposed();
72+
73+
return _rentedBuffer.Length - _index;
74+
}
75+
}
76+
77+
public void Clear()
78+
{
79+
CheckIfDisposed();
80+
81+
ClearHelper();
82+
}
83+
84+
private void ClearHelper()
85+
{
86+
Debug.Assert(_rentedBuffer != null);
87+
88+
_rentedBuffer.AsSpan(0, _index).Clear();
89+
_index = 0;
90+
}
91+
92+
// Returns the rented buffer back to the pool
93+
public void Dispose()
94+
{
95+
if (_rentedBuffer == null)
96+
{
97+
return;
98+
}
99+
100+
ClearHelper();
101+
ArrayPool<T>.Shared.Return(_rentedBuffer);
102+
_rentedBuffer = null;
103+
}
104+
105+
[MemberNotNull(nameof(_rentedBuffer))]
106+
private void CheckIfDisposed()
107+
{
108+
if (_rentedBuffer == null)
109+
{
110+
ThrowObjectDisposed();
111+
}
112+
113+
[DoesNotReturn]
114+
[MethodImpl(MethodImplOptions.NoInlining)]
115+
static void ThrowObjectDisposed()
116+
{
117+
throw new ObjectDisposedException(nameof(ArrayBufferWriter<T>));
118+
}
119+
}
120+
121+
public void Advance(int count)
122+
{
123+
CheckIfDisposed();
124+
125+
ArgHelper.ThrowIfNegative(count);
126+
127+
if (_index > _rentedBuffer.Length - count)
128+
{
129+
ThrowCannotAdvance(_rentedBuffer.Length);
130+
}
131+
132+
_index += count;
133+
134+
[DoesNotReturn]
135+
[MethodImpl(MethodImplOptions.NoInlining)]
136+
static void ThrowCannotAdvance(int capacity)
137+
{
138+
throw new InvalidOperationException(SR.FormatCannot_advance_past_end_of_the_buffer_which_has_a_size_of_0(capacity));
139+
}
140+
}
141+
142+
public Memory<T> GetMemory(int sizeHint = 0)
143+
{
144+
CheckIfDisposed();
145+
146+
CheckAndResizeBuffer(sizeHint);
147+
return _rentedBuffer.AsMemory(_index);
148+
}
149+
150+
public Span<T> GetSpan(int sizeHint = 0)
151+
{
152+
CheckIfDisposed();
153+
154+
CheckAndResizeBuffer(sizeHint);
155+
return _rentedBuffer.AsSpan(_index);
156+
}
157+
158+
private void CheckAndResizeBuffer(int sizeHint)
159+
{
160+
Debug.Assert(_rentedBuffer != null);
161+
162+
ArgHelper.ThrowIfNegative(sizeHint);
163+
164+
if (sizeHint == 0)
165+
{
166+
sizeHint = MinimumBufferSize;
167+
}
168+
169+
var availableSpace = _rentedBuffer!.Length - _index;
170+
171+
if (sizeHint > availableSpace)
172+
{
173+
var growBy = Math.Max(sizeHint, _rentedBuffer.Length);
174+
175+
var newSize = checked(_rentedBuffer.Length + growBy);
176+
177+
var oldBuffer = _rentedBuffer;
178+
179+
_rentedBuffer = ArrayPool<T>.Shared.Rent(newSize);
180+
181+
Debug.Assert(oldBuffer.Length >= _index);
182+
Debug.Assert(_rentedBuffer.Length >= _index);
183+
184+
var previousBuffer = oldBuffer.AsSpan(0, _index);
185+
previousBuffer.CopyTo(_rentedBuffer);
186+
previousBuffer.Clear();
187+
ArrayPool<T>.Shared.Return(oldBuffer);
188+
}
189+
190+
Debug.Assert(_rentedBuffer.Length - _index > 0);
191+
Debug.Assert(_rentedBuffer.Length - _index >= sizeHint);
192+
}
193+
}

0 commit comments

Comments
 (0)