From 42c642d4d4fa07e3e8af71ca583504ea9c9a0d74 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 24 Jul 2026 08:53:22 -0700 Subject: [PATCH 1/2] Re-enable the ArrayPool PollingEventFires test The original failure (#44037) was a remote-process hang against the old ArrayPool implementation, which has since been rewritten. Harden the poll check against the Gen2GcCallback finalizer timing -- a single gen2 collection isn't guaranteed to run the callback that emits the poll event -- by retrying a bounded number of times. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Buffers.Tests/ArrayPool/CollectionTests.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs b/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs index 528d733d026640..8f5d02e3992cd7 100644 --- a/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs +++ b/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs @@ -152,7 +152,6 @@ public unsafe void ThreadLocalIsCollectedUnderNormalPressure() private static bool IsPreciseGcSupportedAndRemoteExecutorSupported => PlatformDetection.IsPreciseGcSupported && RemoteExecutor.IsSupported; - [ActiveIssue("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/dotnet/runtime/issues/44037")] [ConditionalFact(typeof(CollectionTests), nameof(IsPreciseGcSupportedAndRemoteExecutorSupported))] public void PollingEventFires() { @@ -179,10 +178,15 @@ public void PollingEventFires() Assert.False(pollEventFired, "collection isn't hooked up until the first item is returned"); ArrayPool.Shared.Return(buffer); + // The poll event is emitted from a Gen2GcCallback finalizer, so a single gen2 + // collection isn't guaranteed to run it. Retry until it fires or we give up. RunWithListener(() => { - GC.Collect(2); - GC.WaitForPendingFinalizers(); + for (int i = 0; i < 10 && !pollEventFired; i++) + { + GC.Collect(2); + GC.WaitForPendingFinalizers(); + } }, EventLevel.Informational, e => From dccaeb4484b62a83b9b1aeeded05956dead41dab Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 24 Jul 2026 09:44:53 -0700 Subject: [PATCH 2/2] Use Volatile access for the cross-thread poll flag The listener callback runs on the finalizer thread, so read/write the flag with Volatile to avoid a visibility race with the retry loop. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../ArrayPool/CollectionTests.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs b/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs index 8f5d02e3992cd7..fa391e1002aa23 100644 --- a/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs +++ b/src/libraries/System.Runtime/tests/System.Buffers.Tests/ArrayPool/CollectionTests.cs @@ -5,6 +5,7 @@ using System.Diagnostics.Tracing; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; using Microsoft.DotNet.RemoteExecutor; @@ -157,7 +158,9 @@ public void PollingEventFires() { RemoteInvokeWithTrimming(() => { - bool pollEventFired = false; + // The listener callback fires on the finalizer thread (Trim runs from a + // Gen2GcCallback), so access the flag with Volatile to observe it reliably. + StrongBox pollEventFired = new(false); float[] buffer = ArrayPool.Shared.Rent(10); // Polling doesn't start until the thread locals are created for a pool. @@ -172,17 +175,17 @@ public void PollingEventFires() e => { if (e.EventId == EventIds.BufferTrimPoll) - pollEventFired = true; + Volatile.Write(ref pollEventFired.Value, true); }); - Assert.False(pollEventFired, "collection isn't hooked up until the first item is returned"); + Assert.False(Volatile.Read(ref pollEventFired.Value), "collection isn't hooked up until the first item is returned"); ArrayPool.Shared.Return(buffer); // The poll event is emitted from a Gen2GcCallback finalizer, so a single gen2 // collection isn't guaranteed to run it. Retry until it fires or we give up. RunWithListener(() => { - for (int i = 0; i < 10 && !pollEventFired; i++) + for (int i = 0; i < 10 && !Volatile.Read(ref pollEventFired.Value); i++) { GC.Collect(2); GC.WaitForPendingFinalizers(); @@ -192,11 +195,11 @@ public void PollingEventFires() e => { if (e.EventId == EventIds.BufferTrimPoll) - pollEventFired = true; + Volatile.Write(ref pollEventFired.Value, true); }); // Polling events should only fire when trimming is enabled - Assert.True(pollEventFired); + Assert.True(Volatile.Read(ref pollEventFired.Value)); }); } }