-
Notifications
You must be signed in to change notification settings - Fork 621
Flaky CI: GC-dependent lifecycle tests fail intermittently on .NET Framework #3609
Description
Problem
Several tests that verify object lifecycle and GC behavior fail intermittently on Windows .NET Framework. They all share the same pattern: call CollectGarbage(), then assert that objects have been finalized or reference counts have changed.
Failing tests (from 10 recent main builds)
| Test | Failures | Error |
|---|---|---|
SKColorSpaceTest.ColorSpaceIsNotDisposedPrematurely |
2x | Expected refcount 4, got 3 |
SKImageTest.DataOutLivesImageUntilFinalizersRun |
1x | Expected refcount 3, got 2 |
SKCodecTest.StreamLosesOwnershipAndCanBeGarbageCollected |
1x | Assert.True — object not collected |
SKTypefaceTest.StreamLosesOwnershipAndCanBeGarbageCollected |
1x | Assert.True — object not collected |
SKManagedWStreamTest.StreamIsNotDisposedWhenReferencedIsDisposed |
1x | Assert.True — stream unexpectedly disposed |
SKManagedWStreamTest.StreamIsReferencedAndNotDisposedPrematurely |
1x | NullReferenceException (GC race) |
Already skipped on Mono
Several of these tests call SkipOnMono(), acknowledging that GC behavior varies by runtime. The same non-determinism applies to .NET Framework under CI load.
Root Cause
All these tests assert specific GC/finalizer behavior:
CollectGarbage()+ assert object is collected — GC is non-deterministic, especially on .NET Framework under heavy CI load with concurrent test execution. Finalizers may not run within the expected window.- Reference count assertions — Tests check native refcounts at specific points, but concurrent finalizer threads can alter refcounts between the
CollectGarbage()call and the assertion. - The
NullReferenceExceptioninStreamIsReferencedAndNotDisposedPrematurelysuggests a race where the finalizer disposes the stream between the GC call and the assertion.
Impact
Low individually (each fails ~1-2x per 10 builds), but collectively they contribute to CI noise. Combined with the x86 memory exhaustion tests (#3608), they make the Tests stage fail on nearly every build.
Recommendations
-
Add retry/tolerance to GC assertions — Instead of a single
CollectGarbage()+ assert, retry the GC+assert loop a few times:// Instead of: CollectGarbage(); Assert.True(collected); // Use: for (int i = 0; i < 5; i++) { CollectGarbage(); if (collected) break; Thread.Sleep(100); } Assert.True(collected);
-
Make reference count checks tolerant — For
ColorSpaceIsNotDisposedPrematurely, the exact refcount may vary by ±1 depending on internal caching. ConsiderAssert.InRangeinstead ofAssert.Equal. -
Gate on runtime — If .NET Framework's GC is too unpredictable, consider
SkipOnNetFramework()for the most fragile tests, similar to the existingSkipOnMono(). -
Improve
CollectGarbage()helper — If it doesn't already, ensure it callsGC.Collect(),GC.WaitForPendingFinalizers(),GC.Collect()(double-collect pattern) and possibly with a small delay.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status