diff --git a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
index f165e6f82489..03c899191442 100644
--- a/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
+++ b/src/Components/Components/src/Microsoft.AspNetCore.Components.csproj
@@ -26,6 +26,7 @@
+
diff --git a/src/Components/Components/src/NavigationManager.cs b/src/Components/Components/src/NavigationManager.cs
index fecbfaca6c28..8f8a11e79128 100644
--- a/src/Components/Components/src/NavigationManager.cs
+++ b/src/Components/Components/src/NavigationManager.cs
@@ -447,7 +447,7 @@ protected async ValueTask NotifyLocationChangingAsync(string uri, string?
}
finally
{
- ArrayPool>.Shared.Return(locationChangingHandlersCopy);
+ ArrayPool>.Shared.Return(locationChangingHandlersCopy, handlerCount);
}
}
diff --git a/src/Components/Endpoints/src/FormMapping/Converters/CollectionAdapters/ArrayPoolBufferAdapter.cs b/src/Components/Endpoints/src/FormMapping/Converters/CollectionAdapters/ArrayPoolBufferAdapter.cs
index 036d3308f34c..679c35de4844 100644
--- a/src/Components/Endpoints/src/FormMapping/Converters/CollectionAdapters/ArrayPoolBufferAdapter.cs
+++ b/src/Components/Endpoints/src/FormMapping/Converters/CollectionAdapters/ArrayPoolBufferAdapter.cs
@@ -17,7 +17,7 @@ public static PooledBuffer Add(ref PooledBuffer buffer, TElement element)
{
var newBuffer = ArrayPool.Shared.Rent(buffer.Data.Length * 2);
Array.Copy(buffer.Data, newBuffer, buffer.Data.Length);
- ArrayPool.Shared.Return(buffer.Data);
+ ArrayPool.Shared.ReturnAndClearReferences(buffer.Data, buffer.Count);
buffer.Data = newBuffer;
}
@@ -28,7 +28,7 @@ public static PooledBuffer Add(ref PooledBuffer buffer, TElement element)
public static TCollection ToResult(PooledBuffer buffer)
{
var result = TCollectionFactory.ToResultCore(buffer.Data, buffer.Count);
- ArrayPool.Shared.Return(buffer.Data);
+ ArrayPool.Shared.ReturnAndClearReferences(buffer.Data, buffer.Count);
return result;
}
diff --git a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs
index 6b6775af4be3..291badc2a312 100644
--- a/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs
+++ b/src/Components/Endpoints/src/FormMapping/PrefixResolver.cs
@@ -38,7 +38,7 @@ public void Dispose()
{
if (_sortedKeys != null)
{
- ArrayPool.Shared.Return(_sortedKeys);
+ ArrayPool.Shared.Return(_sortedKeys, _length);
}
}
diff --git a/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj b/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj
index de0329c94e63..0788fc482e4d 100644
--- a/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj
+++ b/src/Components/Endpoints/src/Microsoft.AspNetCore.Components.Endpoints.csproj
@@ -35,9 +35,8 @@
-
-
+
diff --git a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
index 37e17a0f0f49..c57c44a9d9fa 100644
--- a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
+++ b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
@@ -32,6 +32,7 @@ Microsoft.AspNetCore.Http.HttpResponse
+
diff --git a/src/Middleware/OutputCaching/src/Microsoft.AspNetCore.OutputCaching.csproj b/src/Middleware/OutputCaching/src/Microsoft.AspNetCore.OutputCaching.csproj
index 55cc936fec7b..8a0891b78695 100644
--- a/src/Middleware/OutputCaching/src/Microsoft.AspNetCore.OutputCaching.csproj
+++ b/src/Middleware/OutputCaching/src/Microsoft.AspNetCore.OutputCaching.csproj
@@ -23,6 +23,7 @@
+
diff --git a/src/Middleware/OutputCaching/src/OutputCacheEntryFormatter.cs b/src/Middleware/OutputCaching/src/OutputCacheEntryFormatter.cs
index 6a7862fc1488..144c0d1b59aa 100644
--- a/src/Middleware/OutputCaching/src/OutputCacheEntryFormatter.cs
+++ b/src/Middleware/OutputCaching/src/OutputCacheEntryFormatter.cs
@@ -46,10 +46,11 @@ public static async ValueTask StoreAsync(string key, OutputCacheEntry value, Has
{
if (store is IOutputCacheBufferStore bufferStore)
{
- await bufferStore.SetAsync(key, new(buffer.GetCommittedMemory()), CopyToLeasedMemory(tags, out var lease), duration, cancellationToken);
+ ReadOnlyMemory leasedTags = CopyToLeasedMemory(tags, out var lease);
+ await bufferStore.SetAsync(key, new(buffer.GetCommittedMemory()), leasedTags, duration, cancellationToken);
if (lease is not null)
{
- ArrayPool.Shared.Return(lease);
+ ArrayPool.Shared.Return(lease, leasedTags.Length);
}
}
else
diff --git a/src/Middleware/OutputCaching/src/RecyclableArrayBufferWriter.cs b/src/Middleware/OutputCaching/src/RecyclableArrayBufferWriter.cs
index 2cb63f0de360..504c1ef22576 100644
--- a/src/Middleware/OutputCaching/src/RecyclableArrayBufferWriter.cs
+++ b/src/Middleware/OutputCaching/src/RecyclableArrayBufferWriter.cs
@@ -32,11 +32,12 @@ public RecyclableArrayBufferWriter()
public void Dispose()
{
var tmp = _buffer;
+ var count = _index;
_index = 0;
_buffer = Array.Empty();
if (tmp.Length != 0)
{
- ArrayPool.Shared.Return(tmp);
+ ArrayPool.Shared.ReturnAndClearReferences(tmp, count);
}
}
@@ -120,7 +121,7 @@ private void CheckAndResizeBuffer(int sizeHint)
oldArray.AsSpan(0, _index).CopyTo(_buffer);
if (oldArray.Length != 0)
{
- ArrayPool.Shared.Return(oldArray);
+ ArrayPool.Shared.ReturnAndClearReferences(oldArray, _index);
}
}
diff --git a/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj b/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj
index d27fdfb88622..ee333c943545 100644
--- a/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj
+++ b/src/Middleware/WebSockets/src/Microsoft.AspNetCore.WebSockets.csproj
@@ -19,6 +19,7 @@
+
diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs b/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs
index 876c93a9f3f7..8ecd3fc9188e 100644
--- a/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs
+++ b/src/Mvc/Mvc.NewtonsoftJson/src/JsonArrayPool.cs
@@ -26,6 +26,6 @@ public void Return(T[]? array)
{
ArgumentNullException.ThrowIfNull(array);
- _inner.Return(array);
+ _inner.ReturnAndClearReferences(array, array.Length);
}
}
diff --git a/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj b/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj
index 71271939fa15..c79557643ddc 100644
--- a/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj
+++ b/src/Mvc/Mvc.NewtonsoftJson/src/Microsoft.AspNetCore.Mvc.NewtonsoftJson.csproj
@@ -26,6 +26,7 @@
+
diff --git a/src/Shared/Buffers/ArrayPoolExtensions.cs b/src/Shared/Buffers/ArrayPoolExtensions.cs
new file mode 100644
index 000000000000..08d1fdf9f4be
--- /dev/null
+++ b/src/Shared/Buffers/ArrayPoolExtensions.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers;
+
+internal static class ArrayPoolExtensions
+{
+ ///
+ /// Clears the specified range and returns the array to the pool.
+ ///
+ public static void Return(this ArrayPool pool, T[] array, int lengthToClear)
+ {
+ array.AsSpan(0, lengthToClear).Clear();
+ pool.Return(array);
+ }
+
+ ///
+ /// Clears the specified range if is a reference type or
+ /// contains references and returns the array to the pool.
+ ///
+ ///
+ /// For .NET Framework, falls back to checking if is not a primitive type
+ /// where RuntimeHelpers.IsReferenceOrContainsReferences<T>() is not available.
+ ///
+ public static void ReturnAndClearReferences(this ArrayPool pool, T[] array, int lengthToClear)
+ {
+#if NET
+ if (RuntimeHelpers.IsReferenceOrContainsReferences())
+#else
+ if (!typeof(T).IsPrimitive)
+#endif
+ {
+ array.AsSpan(0, lengthToClear).Clear();
+ }
+
+ pool.Return(array);
+ }
+}
diff --git a/src/Shared/ValueStringBuilder/ValueListBuilder.cs b/src/Shared/ValueStringBuilder/ValueListBuilder.cs
index 19b0189a3f6b..bb8630ea338b 100644
--- a/src/Shared/ValueStringBuilder/ValueListBuilder.cs
+++ b/src/Shared/ValueStringBuilder/ValueListBuilder.cs
@@ -60,7 +60,7 @@ public void Dispose()
if (toReturn != null)
{
_arrayFromPool = null;
- ArrayPool.Shared.Return(toReturn);
+ ArrayPool.Shared.ReturnAndClearReferences(toReturn, _pos);
}
}
@@ -95,7 +95,7 @@ private void Grow(int additionalCapacityRequired = 1)
_span = _arrayFromPool = array;
if (toReturn != null)
{
- ArrayPool.Shared.Return(toReturn);
+ ArrayPool.Shared.ReturnAndClearReferences(toReturn, _pos);
}
}
}
diff --git a/src/Shared/test/Shared.Tests/ArrayPoolExtensionsTests.cs b/src/Shared/test/Shared.Tests/ArrayPoolExtensionsTests.cs
new file mode 100644
index 000000000000..869b28a39029
--- /dev/null
+++ b/src/Shared/test/Shared.Tests/ArrayPoolExtensionsTests.cs
@@ -0,0 +1,266 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Buffers;
+
+public sealed class ArrayPoolExtensionsTests
+{
+ private record struct StructWithStringField(string Value);
+
+ [Fact]
+ public void Return_PartiallyClearsArray_UnmanagedType_WithPartialLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ int[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(int.MaxValue);
+
+ pool.Return(array, 42);
+
+ Assert.True(array.AsSpan(0, 42).IndexOfAnyExcept(0) < 0);
+ Assert.True(array.AsSpan(42).IndexOfAnyExcept(int.MaxValue) < 0);
+ }
+
+ [Fact]
+ public void Return_PartiallyClearsArray_ManagedValueType_WithPartialLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ StructWithStringField[] array = pool.Rent(64);
+
+ StructWithStringField value = new("abc");
+ array.AsSpan().Fill(value);
+
+ pool.Return(array, 42);
+
+ Assert.True(array.AsSpan(0, 42).IndexOfAnyExcept(default(StructWithStringField)) < 0);
+ Assert.True(array.AsSpan(42).IndexOfAnyExcept(value) < 0);
+ }
+
+ [Fact]
+ public void Return_PartiallyClearsArray_ReferenceType_WithPartialLengthSpecified()
+ {
+ const string Value = "abc";
+
+ ArrayPool pool = ArrayPool.Create();
+ string[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(Value);
+
+ pool.Return(array, 42);
+
+ Assert.True(array.AsSpan(0, 42).IndexOfAnyExcept((string)null) < 0);
+ Assert.True(array.AsSpan(42).IndexOfAnyExcept(Value) < 0);
+ }
+
+ [Fact]
+ public void Return_CompletelyClearsArray_UnmanagedType_WithFullLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ int[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(int.MaxValue);
+
+ pool.Return(array, array.Length);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(0) < 0);
+ }
+
+ [Fact]
+ public void Return_CompletelyClearsArray_ManagedValueType_WithFullLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ StructWithStringField[] array = pool.Rent(64);
+
+ StructWithStringField value = new("abc");
+ array.AsSpan().Fill(value);
+
+ pool.Return(array, array.Length);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(default(StructWithStringField)) < 0);
+ }
+
+ [Fact]
+ public void Return_CompletelyClearsArray_ReferenceType_WithFullLengthSpecified()
+ {
+ const string Value = "abc";
+
+ ArrayPool pool = ArrayPool.Create();
+ string[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(Value);
+
+ pool.Return(array, array.Length);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept((string)null) < 0);
+ }
+
+ [Fact]
+ public void Return_DoesNotClearArray_UnmanagedType_WithZeroLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ int[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(int.MaxValue);
+
+ pool.Return(array, 0);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(int.MaxValue) < 0);
+ }
+
+ [Fact]
+ public void Return_DoesNotClearArray_ManagedValueType_WithZeroLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ StructWithStringField[] array = pool.Rent(64);
+
+ StructWithStringField value = new("abc");
+ array.AsSpan().Fill(value);
+
+ pool.Return(array, 0);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(value) < 0);
+ }
+
+ [Fact]
+ public void Return_DoesNotClearArray_ReferenceType_WithZeroLengthSpecified()
+ {
+ const string Value = "abc";
+
+ ArrayPool pool = ArrayPool.Create();
+ string[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(Value);
+
+ pool.Return(array, 0);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(Value) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_PartiallyClearsArray_ReferenceType_WithPartialLengthSpecified()
+ {
+ const string Value = "abc";
+
+ ArrayPool pool = ArrayPool.Create();
+ string[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(Value);
+
+ pool.ReturnAndClearReferences(array, 42);
+
+ Assert.True(array.AsSpan(0, 42).IndexOfAnyExcept((string)null) < 0);
+ Assert.True(array.AsSpan(42).IndexOfAnyExcept(Value) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_PartiallyClearsArray_ManagedValueType_WithPartialLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ StructWithStringField[] array = pool.Rent(64);
+
+ StructWithStringField value = new("abc");
+ array.AsSpan().Fill(value);
+
+ pool.ReturnAndClearReferences(array, 42);
+
+ Assert.True(array.AsSpan(0, 42).IndexOfAnyExcept(default(StructWithStringField)) < 0);
+ Assert.True(array.AsSpan(42).IndexOfAnyExcept(value) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_DoesNotClearArray_UnmanagedType_WithPartialLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ int[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(int.MaxValue);
+
+ pool.ReturnAndClearReferences(array, 42);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(int.MaxValue) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_CompletelyClearsArray_ReferenceType_WithFullLengthSpecified()
+ {
+ const string Value = "abc";
+
+ ArrayPool pool = ArrayPool.Create();
+ string[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(Value);
+
+ pool.ReturnAndClearReferences(array, array.Length);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept((string)null) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_CompletelyClearsArray_ManagedValueType_WithFullLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ StructWithStringField[] array = pool.Rent(64);
+
+ StructWithStringField value = new("abc");
+ array.AsSpan().Fill(value);
+
+ pool.ReturnAndClearReferences(array, array.Length);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(default(StructWithStringField)) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_DoesNotClearArray_UnmanagedType_WithFullLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ int[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(int.MaxValue);
+
+ pool.ReturnAndClearReferences(array, array.Length);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(int.MaxValue) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_DoesNotClearArray_ReferenceType_WithZeroLengthSpecified()
+ {
+ const string Value = "abc";
+
+ ArrayPool pool = ArrayPool.Create();
+ string[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(Value);
+
+ pool.ReturnAndClearReferences(array, 0);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(Value) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_DoesNotClearArray_ManagedValueType_WithZeroLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ StructWithStringField[] array = pool.Rent(64);
+
+ StructWithStringField value = new("abc");
+ array.AsSpan().Fill(value);
+
+ pool.ReturnAndClearReferences(array, 0);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(value) < 0);
+ }
+
+ [Fact]
+ public void ReturnAndClearReferences_DoesNotClearArray_UnmanagedType_WithZeroLengthSpecified()
+ {
+ ArrayPool pool = ArrayPool.Create();
+ int[] array = pool.Rent(64);
+
+ array.AsSpan().Fill(int.MaxValue);
+
+ pool.ReturnAndClearReferences(array, 0);
+
+ Assert.True(array.AsSpan().IndexOfAnyExcept(int.MaxValue) < 0);
+ }
+}
diff --git a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj
index 72496024ef3b..a19a0184032f 100644
--- a/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj
+++ b/src/Shared/test/Shared.Tests/Microsoft.AspNetCore.Shared.Tests.csproj
@@ -42,6 +42,7 @@
+
diff --git a/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj b/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj
index 6b621b28bc00..1900a3da8e95 100644
--- a/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj
+++ b/src/SignalR/common/Protocols.NewtonsoftJson/src/Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson.csproj
@@ -18,6 +18,7 @@
+
diff --git a/src/SignalR/common/Shared/JsonUtils.cs b/src/SignalR/common/Shared/JsonUtils.cs
index f5253fd02ba3..9fa18a589459 100644
--- a/src/SignalR/common/Shared/JsonUtils.cs
+++ b/src/SignalR/common/Shared/JsonUtils.cs
@@ -216,7 +216,7 @@ public void Return(T[]? array)
return;
}
- _inner.Return(array);
+ _inner.ReturnAndClearReferences(array, array.Length);
}
}
}