From c90b33221feb67f850800373de9dcf896d94de78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 04:19:37 +0000 Subject: [PATCH 1/4] Initial plan From bb556c74812bd222a739371071e953cc99ad0cf4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 04:24:08 +0000 Subject: [PATCH 2/4] Fix flaky background Gen2 GC detection in GcRestrictedCalloutReversePInvoke test Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../GcRestrictedCalloutReversePInvoke.csproj | 5 ++++ .../Program.cs | 23 ++++--------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj index 7c230bb041ed9d..9d1126895c8892 100644 --- a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj +++ b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj @@ -15,6 +15,11 @@ + + + + + diff --git a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs index 5b7677ebe0c56f..e004605ad72b67 100644 --- a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs +++ b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs @@ -32,37 +32,24 @@ public static void TestEntryPoint() private static void ForceBackgroundGen2Collection(TimeSpan timeout) { - int initialGen2Collections = GC.CollectionCount(2); + long initialBackgroundGcIndex = GC.GetGCMemoryInfo(GCKind.Background).Index; using AllocationPressure pressure = new(); pressure.Start(); Stopwatch stopwatch = Stopwatch.StartNew(); - bool observedConcurrentGen2 = false; while (stopwatch.Elapsed < timeout) { - int previousGen2Collections = GC.CollectionCount(2); GC.Collect(2, GCCollectionMode.Forced, blocking: false, compacting: false); - if (!SpinWait.SpinUntil(() => GC.CollectionCount(2) > previousGen2Collections, TimeSpan.FromMilliseconds(500))) + if (SpinWait.SpinUntil(() => GC.GetGCMemoryInfo(GCKind.Background).Index > initialBackgroundGcIndex, TimeSpan.FromMilliseconds(500))) { - continue; - } - - GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(); - if (memoryInfo.Generation == 2 && memoryInfo.Concurrent) - { - observedConcurrentGen2 = true; - break; + return; } } - pressure.Stop(); - - if (!observedConcurrentGen2) - { - throw new Exception($"Timed out after {timeout} waiting for a concurrent Gen2 GC. Initial count: {initialGen2Collections}, final count: {GC.CollectionCount(2)}."); - } + long finalBackgroundGcIndex = GC.GetGCMemoryInfo(GCKind.Background).Index; + throw new Exception($"Timed out after {timeout} waiting for a background GC. Initial index: {initialBackgroundGcIndex}, final index: {finalBackgroundGcIndex}."); } } From 6d5801efc4d2c6cca92aa130e2181f3dfe90d842 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 04:57:11 +0000 Subject: [PATCH 3/4] Poll background GC index with a short sleep instead of a tight spin Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../GcRestrictedCalloutReversePInvoke/Program.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs index e004605ad72b67..9b500d2a23b6cd 100644 --- a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs +++ b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs @@ -42,9 +42,20 @@ private static void ForceBackgroundGen2Collection(TimeSpan timeout) { GC.Collect(2, GCCollectionMode.Forced, blocking: false, compacting: false); - if (SpinWait.SpinUntil(() => GC.GetGCMemoryInfo(GCKind.Background).Index > initialBackgroundGcIndex, TimeSpan.FromMilliseconds(500))) + // Poll with a short sleep rather than a tight spin. GC.GetGCMemoryInfo(GCKind.Background) + // reads state that the GC engine updates non-atomically as a background collection + // finishes; querying it as fast as possible increases the odds of observing a + // transiently inconsistent record and hitting an unrelated GC-engine assert in Checked + // builds. A short delay between polls avoids that without noticeably slowing detection. + Stopwatch pollStopwatch = Stopwatch.StartNew(); + while (pollStopwatch.Elapsed < TimeSpan.FromMilliseconds(500)) { - return; + if (GC.GetGCMemoryInfo(GCKind.Background).Index > initialBackgroundGcIndex) + { + return; + } + + Thread.Sleep(15); } } From ba0a5153180a435183adadc30ed0e9d67de1f140 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 31 Aug 2026 06:46:47 +0000 Subject: [PATCH 4/4] Delete GcRestrictedCalloutReversePInvoke test per maintainer request Co-authored-by: MichalStrehovsky <13110571+MichalStrehovsky@users.noreply.github.com> --- .../GcRestrictedCalloutReversePInvoke.csproj | 26 ---- .../Program.cs | 126 ------------------ 2 files changed, 152 deletions(-) delete mode 100644 src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj delete mode 100644 src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs diff --git a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj deleted file mode 100644 index 9d1126895c8892..00000000000000 --- a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/GcRestrictedCalloutReversePInvoke.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - 0 - true - - true - true - - true - true - - - - - - - - - - - - - - - - diff --git a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs b/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs deleted file mode 100644 index 9b500d2a23b6cd..00000000000000 --- a/src/tests/Interop/COM/ComWrappers/GcRestrictedCalloutReversePInvoke/Program.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections; -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Threading; -using ComWrappersTests.Common; -using Xunit; -using TestLibrary; - -public class Program -{ - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsMultithreadingSupported))] - [SkipOnCoreClr("This test is not compatible with GC stress.", RuntimeTestModes.AnyGCStress)] - public static void TestEntryPoint() - { - ComWrappers.RegisterForTrackerSupport(TrackerComWrappers.Instance); - - IntPtr trackerObject = MockReferenceTrackerRuntime.CreateTrackerObject(); - object wrapper = TrackerComWrappers.Instance.GetOrCreateObjectForComInstance(trackerObject, CreateObjectFlags.TrackerObject); - Marshal.Release(trackerObject); - - // dotnet/runtime#110683 - // Before the runtime fix, assertion-enabled NativeAOT runtimes can fail with - // ASSERT(ThreadStore::IsTrapThreadsRequested()) when this managed GC restricted - // callout is reverse-invoked on a background GC thread. - ForceBackgroundGen2Collection(timeout: TimeSpan.FromSeconds(30)); - GC.KeepAlive(wrapper); - } - - private static void ForceBackgroundGen2Collection(TimeSpan timeout) - { - long initialBackgroundGcIndex = GC.GetGCMemoryInfo(GCKind.Background).Index; - using AllocationPressure pressure = new(); - pressure.Start(); - - Stopwatch stopwatch = Stopwatch.StartNew(); - - while (stopwatch.Elapsed < timeout) - { - GC.Collect(2, GCCollectionMode.Forced, blocking: false, compacting: false); - - // Poll with a short sleep rather than a tight spin. GC.GetGCMemoryInfo(GCKind.Background) - // reads state that the GC engine updates non-atomically as a background collection - // finishes; querying it as fast as possible increases the odds of observing a - // transiently inconsistent record and hitting an unrelated GC-engine assert in Checked - // builds. A short delay between polls avoids that without noticeably slowing detection. - Stopwatch pollStopwatch = Stopwatch.StartNew(); - while (pollStopwatch.Elapsed < TimeSpan.FromMilliseconds(500)) - { - if (GC.GetGCMemoryInfo(GCKind.Background).Index > initialBackgroundGcIndex) - { - return; - } - - Thread.Sleep(15); - } - } - - long finalBackgroundGcIndex = GC.GetGCMemoryInfo(GCKind.Background).Index; - throw new Exception($"Timed out after {timeout} waiting for a background GC. Initial index: {initialBackgroundGcIndex}, final index: {finalBackgroundGcIndex}."); - } -} - -sealed class AllocationPressure : IDisposable -{ - private readonly ManualResetEventSlim _stopSignal = new(initialState: false); - private readonly Thread _allocatorThread; - - public AllocationPressure() - { - _allocatorThread = new Thread(AllocateUntilStopped) - { - IsBackground = true, - Name = "GcRestrictedCalloutReversePInvoke_AllocationPressure" - }; - } - - public void Start() => _allocatorThread.Start(); - - public void Stop() - { - _stopSignal.Set(); - if (!_allocatorThread.Join(millisecondsTimeout: 5000)) - { - throw new Exception("Allocation pressure thread did not stop in time."); - } - } - - private void AllocateUntilStopped() - { - byte[][] ring = new byte[64][]; - int index = 0; - - while (!_stopSignal.IsSet) - { - ring[index] = new byte[256 * 1024]; - index = (index + 1) % ring.Length; - } - } - - public void Dispose() - { - Stop(); - _stopSignal.Dispose(); - } -} - -sealed class TrackerComWrappers : ComWrappers -{ - public static TrackerComWrappers Instance { get; } = new(); - - protected unsafe override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count) - { - count = 0; - return null; - } - - protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags) => new object(); - - protected override void ReleaseObjects(IEnumerable objects) - { - } -}