-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Improve Http2Connection buffer management #79484
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
|
||
using System.Buffers; | ||
using System.Diagnostics; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
|
||
namespace System.Net | ||
|
@@ -30,8 +31,12 @@ internal struct ArrayBuffer : IDisposable | |
|
||
public ArrayBuffer(int initialSize, bool usePool = false) | ||
{ | ||
Debug.Assert(initialSize > 0 || usePool); | ||
|
||
_usePool = usePool; | ||
_bytes = usePool ? ArrayPool<byte>.Shared.Rent(initialSize) : new byte[initialSize]; | ||
_bytes = initialSize == 0 | ||
? Array.Empty<byte>() | ||
: usePool ? ArrayPool<byte>.Shared.Rent(initialSize) : new byte[initialSize]; | ||
Comment on lines
+37
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note ArrayPool will use Array.Empty for 0 byte requests, so this could also be: _bytes =
usePool ? ArrayPool<byte>.Shared.Rent(initialSize) :
initialSize == 0 ? Array.Empty<byte>() :
new byte[initialSize]; It's unlikely to really matter, but if we expect initialSize to most commonly be non-zero, you might want to reorder it. |
||
_activeStart = 0; | ||
_availableStart = 0; | ||
} | ||
|
@@ -54,12 +59,26 @@ public void Dispose() | |
byte[] array = _bytes; | ||
_bytes = null!; | ||
|
||
if (_usePool && array != null) | ||
if (array is not null) | ||
{ | ||
ArrayPool<byte>.Shared.Return(array); | ||
ReturnBufferIfPooled(array); | ||
} | ||
} | ||
|
||
// This is different from Dispose as the instance remains usable afterwards (_bytes will not be null). | ||
public void ClearAndReturnBuffer() | ||
{ | ||
Debug.Assert(_usePool); | ||
Debug.Assert(_bytes is not null); | ||
|
||
_activeStart = 0; | ||
_availableStart = 0; | ||
|
||
byte[] bufferToReturn = _bytes; | ||
_bytes = Array.Empty<byte>(); | ||
ReturnBufferIfPooled(bufferToReturn); | ||
} | ||
|
||
public int ActiveLength => _availableStart - _activeStart; | ||
public Span<byte> ActiveSpan => new Span<byte>(_bytes, _activeStart, _availableStart - _activeStart); | ||
public ReadOnlySpan<byte> ActiveReadOnlySpan => new ReadOnlySpan<byte>(_bytes, _activeStart, _availableStart - _activeStart); | ||
|
@@ -94,10 +113,23 @@ public void Commit(int byteCount) | |
} | ||
|
||
// Ensure at least [byteCount] bytes to write to. | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public void EnsureAvailableSpace(int byteCount) | ||
{ | ||
if (byteCount <= AvailableLength) | ||
if (byteCount > AvailableLength) | ||
{ | ||
EnsureAvailableSpaceCore(byteCount); | ||
} | ||
} | ||
|
||
private void EnsureAvailableSpaceCore(int byteCount) | ||
{ | ||
Debug.Assert(AvailableLength < byteCount); | ||
|
||
if (_bytes.Length == 0) | ||
{ | ||
Debug.Assert(_usePool && _activeStart == 0 && _availableStart == 0); | ||
_bytes = ArrayPool<byte>.Shared.Rent(byteCount); | ||
return; | ||
} | ||
|
||
|
@@ -134,72 +166,24 @@ public void EnsureAvailableSpace(int byteCount) | |
_activeStart = 0; | ||
|
||
_bytes = newBytes; | ||
if (_usePool) | ||
{ | ||
ArrayPool<byte>.Shared.Return(oldBytes); | ||
} | ||
ReturnBufferIfPooled(oldBytes); | ||
|
||
Debug.Assert(byteCount <= AvailableLength); | ||
} | ||
|
||
// Ensure at least [byteCount] bytes to write to, up to the specified limit | ||
public void TryEnsureAvailableSpaceUpToLimit(int byteCount, int limit) | ||
public void Grow() | ||
{ | ||
if (byteCount <= AvailableLength) | ||
{ | ||
return; | ||
} | ||
|
||
int totalFree = _activeStart + AvailableLength; | ||
if (byteCount <= totalFree) | ||
{ | ||
// We can free up enough space by just shifting the bytes down, so do so. | ||
Buffer.BlockCopy(_bytes, _activeStart, _bytes, 0, ActiveLength); | ||
_availableStart = ActiveLength; | ||
_activeStart = 0; | ||
Debug.Assert(byteCount <= AvailableLength); | ||
return; | ||
} | ||
|
||
if (_bytes.Length >= limit) | ||
{ | ||
// Already at limit, can't grow further. | ||
return; | ||
} | ||
|
||
// Double the size of the buffer until we have enough space, or we hit the limit | ||
int desiredSize = Math.Min(ActiveLength + byteCount, limit); | ||
int newSize = _bytes.Length; | ||
do | ||
{ | ||
newSize = Math.Min(newSize * 2, limit); | ||
} while (newSize < desiredSize); | ||
|
||
byte[] newBytes = _usePool ? | ||
ArrayPool<byte>.Shared.Rent(newSize) : | ||
new byte[newSize]; | ||
byte[] oldBytes = _bytes; | ||
|
||
if (ActiveLength != 0) | ||
{ | ||
Buffer.BlockCopy(oldBytes, _activeStart, newBytes, 0, ActiveLength); | ||
} | ||
|
||
_availableStart = ActiveLength; | ||
_activeStart = 0; | ||
|
||
_bytes = newBytes; | ||
if (_usePool) | ||
{ | ||
ArrayPool<byte>.Shared.Return(oldBytes); | ||
} | ||
|
||
Debug.Assert(byteCount <= AvailableLength || desiredSize == limit); | ||
EnsureAvailableSpaceCore(AvailableLength + 1); | ||
} | ||
|
||
public void Grow() | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
private void ReturnBufferIfPooled(byte[] buffer) | ||
{ | ||
EnsureAvailableSpace(AvailableLength + 1); | ||
// The buffer may be Array.Empty<byte>() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ArrayPool is fine with Array.Empty being returned; it just gets ignored. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll remove the extra check, given that we won't be calling |
||
if (_usePool && buffer.Length > 0) | ||
{ | ||
ArrayPool<byte>.Shared.Return(buffer); | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.