Skip to content

Enable trimming TLS arrays in ArrayPool.Shared #56316

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

Merged
merged 3 commits into from
Jul 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 56 additions & 22 deletions src/libraries/System.Buffers/tests/ArrayPool/CollectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,48 +23,51 @@ public void BuffersAreCollectedWhenStale()
const int BufferCount = 8;
const int BufferSize = 1025;

// Get the pool and check our trim setting
var pool = ArrayPool<int>.Shared;

List<int[]> rentedBuffers = new List<int[]>();

// Rent and return a set of buffers
for (int i = 0; i < BufferCount; i++)
{
rentedBuffers.Add(pool.Rent(BufferSize));
rentedBuffers.Add(ArrayPool<int>.Shared.Rent(BufferSize));
}
for (int i = 0; i < BufferCount; i++)
{
pool.Return(rentedBuffers[i]);
ArrayPool<int>.Shared.Return(rentedBuffers[i]);
}

// Rent what we returned and ensure they are the same
for (int i = 0; i < BufferCount; i++)
{
var buffer = pool.Rent(BufferSize);
var buffer = ArrayPool<int>.Shared.Rent(BufferSize);
Assert.Contains(rentedBuffers, item => ReferenceEquals(item, buffer));
}
for (int i = 0; i < BufferCount; i++)
{
pool.Return(rentedBuffers[i]);
ArrayPool<int>.Shared.Return(rentedBuffers[i]);
}

// Trigger a few Gen2 GCs to make sure the pool has appropriately time stamped buffers.
for (int i = 0; i < 2; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}

// Now wait a little over a minute and force a GC to get some buffers returned
Console.WriteLine("Waiting a minute for buffers to go stale...");
Thread.Sleep(61 * 1000);
GC.Collect(2);
GC.Collect();
GC.WaitForPendingFinalizers();
bool foundNewBuffer = false;
for (int i = 0; i < BufferCount; i++)
{
var buffer = pool.Rent(BufferSize);
var buffer = ArrayPool<int>.Shared.Rent(BufferSize);
if (!rentedBuffers.Any(item => ReferenceEquals(item, buffer)))
{
foundNewBuffer = true;
}
}

// Should only have found a new buffer if we're trimming
Assert.True(foundNewBuffer);
}, 3 * 60 * 1000); // This test has to wait for the buffers to go stale (give it three minutes)
}
Expand All @@ -78,21 +81,18 @@ public unsafe void ThreadLocalIsCollectedUnderHighPressure()
{
RemoteInvokeWithTrimming(() =>
{
// Get the pool and check our trim setting
var pool = ArrayPool<byte>.Shared;

// Create our buffer, return it, re-rent it and ensure we have the same one
const int BufferSize = 4097;
var buffer = pool.Rent(BufferSize);
pool.Return(buffer);
Assert.Same(buffer, pool.Rent(BufferSize));
byte[] buffer = ArrayPool<byte>.Shared.Rent(BufferSize);
ArrayPool<byte>.Shared.Return(buffer);
Assert.Same(buffer, ArrayPool<byte>.Shared.Rent(BufferSize));

// Return it and put memory pressure on to get it cleared
pool.Return(buffer);
ArrayPool<byte>.Shared.Return(buffer);

const int AllocSize = 1024 * 1024 * 64;
int PageSize = Environment.SystemPageSize;
var pressureMethod = pool.GetType().GetMethod("GetMemoryPressure", BindingFlags.Static | BindingFlags.NonPublic);
var pressureMethod = ArrayPool<byte>.Shared.GetType().GetMethod("GetMemoryPressure", BindingFlags.Static | BindingFlags.NonPublic);
do
{
Span<byte> native = new Span<byte>(Marshal.AllocHGlobal(AllocSize).ToPointer(), AllocSize);
Expand All @@ -109,10 +109,45 @@ public unsafe void ThreadLocalIsCollectedUnderHighPressure()
GC.WaitForPendingFinalizers();

// Should have a new buffer now
Assert.NotSame(buffer, pool.Rent(BufferSize));
Assert.NotSame(buffer, ArrayPool<byte>.Shared.Rent(BufferSize));
});
}

// This test can cause problems for other tests run in parallel (from other assemblies) as
// it pushes the physical memory usage above 80% temporarily.
[OuterLoop("This is a long running test (over 2 minutes)")]
[ConditionalFact(typeof(RemoteExecutor), nameof(RemoteExecutor.IsSupported))]
public unsafe void ThreadLocalIsCollectedUnderNormalPressure()
{
RemoteInvokeWithTrimming(() =>
{
// Create our buffer, return it, re-rent it and ensure we have the same one
const int BufferSize = 4097;
byte[] buffer = ArrayPool<byte>.Shared.Rent(BufferSize);
ArrayPool<byte>.Shared.Return(buffer);
Assert.Same(buffer, ArrayPool<byte>.Shared.Rent(BufferSize));

// Return it and put memory pressure on to get it cleared
ArrayPool<byte>.Shared.Return(buffer);

// Make sure buffer gets time stamped
for (int i = 0; i < 2; i++)
{
GC.Collect();
GC.WaitForPendingFinalizers();
}

// Now wait for enough time to pass and force a GC to get buffers dropped
Console.WriteLine("Waiting a minute for buffers to go stale...");
Thread.Sleep(61 * 1000);
GC.Collect();
GC.WaitForPendingFinalizers();

// Should have a new buffer now
Assert.NotSame(buffer, ArrayPool<byte>.Shared.Rent(BufferSize));
}, 3 * 60 * 1000); // This test has to wait for the buffers to go stale (give it three minutes)
}

private static bool IsPreciseGcSupportedAndRemoteExecutorSupported => PlatformDetection.IsPreciseGcSupported && RemoteExecutor.IsSupported;

[ActiveIssue("https://github.com/dotnet/runtime/issues/44037")]
Expand All @@ -121,9 +156,8 @@ public void PollingEventFires()
{
RemoteInvokeWithTrimming(() =>
{
var pool = ArrayPool<float>.Shared;
bool pollEventFired = false;
var buffer = pool.Rent(10);
float[] buffer = ArrayPool<float>.Shared.Rent(10);

// Polling doesn't start until the thread locals are created for a pool.
// Try before the return then after.
Expand All @@ -141,7 +175,7 @@ public void PollingEventFires()
});

Assert.False(pollEventFired, "collection isn't hooked up until the first item is returned");
pool.Return(buffer);
ArrayPool<float>.Shared.Return(buffer);

RunWithListener(() =>
{
Expand Down
Loading