1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Runtime.CompilerServices;
6 using System.Diagnostics;
7 using System.Threading;
8 using System.Runtime;
9 using Xunit;
10 
11 namespace System.Tests
12 {
13     public static partial class GCTests
14     {
15         private static bool s_is32Bits = IntPtr.Size == 4; // Skip IntPtr tests on 32-bit platforms
16 
17         [Fact]
AddMemoryPressure_InvalidBytesAllocated_ThrowsArgumentOutOfRangeException()18         public static void AddMemoryPressure_InvalidBytesAllocated_ThrowsArgumentOutOfRangeException()
19         {
20             AssertExtensions.Throws<ArgumentOutOfRangeException>("bytesAllocated", () => GC.AddMemoryPressure(-1)); // Bytes allocated < 0
21 
22             if (s_is32Bits)
23             {
24                 AssertExtensions.Throws<ArgumentOutOfRangeException>("pressure", () => GC.AddMemoryPressure((long)int.MaxValue + 1)); // Bytes allocated > int.MaxValue on 32 bit platforms
25             }
26         }
27 
28         [Fact]
Collect_Int()29         public static void Collect_Int()
30         {
31             for (int i = 0; i < GC.MaxGeneration + 10; i++)
32             {
33                 GC.Collect(i);
34             }
35         }
36 
37         [Fact]
Collect_Int_NegativeGeneration_ThrowsArgumentOutOfRangeException()38         public static void Collect_Int_NegativeGeneration_ThrowsArgumentOutOfRangeException()
39         {
40             AssertExtensions.Throws<ArgumentOutOfRangeException>("generation", () => GC.Collect(-1)); // Generation < 0
41         }
42 
43         [Theory]
44         [InlineData(GCCollectionMode.Default)]
45         [InlineData(GCCollectionMode.Forced)]
Collect_Int_GCCollectionMode(GCCollectionMode mode)46         public static void Collect_Int_GCCollectionMode(GCCollectionMode mode)
47         {
48             for (int gen = 0; gen <= 2; gen++)
49             {
50                 var b = new byte[1024 * 1024 * 10];
51                 int oldCollectionCount = GC.CollectionCount(gen);
52                 b = null;
53 
54                 GC.Collect(gen, mode);
55 
56                 Assert.True(GC.CollectionCount(gen) > oldCollectionCount);
57             }
58         }
59 
60         [Fact]
Collect_NegativeGenerationCount_ThrowsArgumentOutOfRangeException()61         public static void Collect_NegativeGenerationCount_ThrowsArgumentOutOfRangeException()
62         {
63             AssertExtensions.Throws<ArgumentOutOfRangeException>("generation", () => GC.Collect(-1, GCCollectionMode.Default));
64             AssertExtensions.Throws<ArgumentOutOfRangeException>("generation", () => GC.Collect(-1, GCCollectionMode.Default, false));
65         }
66 
67         [Theory]
68         [InlineData(GCCollectionMode.Default - 1)]
69         [InlineData(GCCollectionMode.Optimized + 1)]
Collection_InvalidCollectionMode_ThrowsArgumentOutOfRangeException(GCCollectionMode mode)70         public static void Collection_InvalidCollectionMode_ThrowsArgumentOutOfRangeException(GCCollectionMode mode)
71         {
72             AssertExtensions.Throws<ArgumentOutOfRangeException>("mode", "Enum value was out of legal range.", () => GC.Collect(2, mode));
73             AssertExtensions.Throws<ArgumentOutOfRangeException>("mode", "Enum value was out of legal range.", () => GC.Collect(2, mode, false));
74         }
75 
76         [Fact]
Collect_CallsFinalizer()77         public static void Collect_CallsFinalizer()
78         {
79             FinalizerTest.Run();
80         }
81 
82         private class FinalizerTest
83         {
84             [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
MakeAndDropTest()85             private static void MakeAndDropTest()
86             {
87                 new TestObject();
88             }
89 
Run()90             public static void Run()
91             {
92                 MakeAndDropTest();
93 
94                 GC.Collect();
95 
96                 // Make sure Finalize() is called
97                 GC.WaitForPendingFinalizers();
98 
99                 Assert.True(TestObject.Finalized);
100             }
101 
102             private class TestObject
103             {
104                 public static bool Finalized { get; private set; }
105 
~TestObject()106                 ~TestObject()
107                 {
108                     Finalized = true;
109                 }
110             }
111         }
112 
113         [Fact]
KeepAlive()114         public static void KeepAlive()
115         {
116             KeepAliveTest.Run();
117         }
118 
119         private class KeepAliveTest
120         {
121             [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
MakeAndDropDNKA()122             private static void MakeAndDropDNKA()
123             {
124                 new DoNotKeepAliveObject();
125             }
126 
Run()127             public static void Run()
128             {
129                 var keepAlive = new KeepAliveObject();
130 
131                 MakeAndDropDNKA();
132 
133                 GC.Collect();
134                 GC.WaitForPendingFinalizers();
135 
136                 Assert.True(DoNotKeepAliveObject.Finalized);
137                 Assert.False(KeepAliveObject.Finalized);
138 
139                 GC.KeepAlive(keepAlive);
140             }
141 
142             private class KeepAliveObject
143             {
144                 public static bool Finalized { get; private set; }
145 
~KeepAliveObject()146                 ~KeepAliveObject()
147                 {
148                     Finalized = true;
149                 }
150             }
151 
152             private class DoNotKeepAliveObject
153             {
154                 public static bool Finalized { get; private set; }
155 
~DoNotKeepAliveObject()156                 ~DoNotKeepAliveObject()
157                 {
158                     Finalized = true;
159                 }
160             }
161         }
162 
163         [Fact]
KeepAlive_Null()164         public static void KeepAlive_Null()
165         {
166             KeepAliveNullTest.Run();
167         }
168 
169         private class KeepAliveNullTest
170         {
171             [System.Runtime.CompilerServices.MethodImplAttribute(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
MakeAndNull()172             private static void MakeAndNull()
173             {
174                 var obj = new TestObject();
175                 obj = null;
176             }
177 
Run()178             public static void Run()
179             {
180                 MakeAndNull();
181 
182                 GC.Collect();
183                 GC.WaitForPendingFinalizers();
184 
185                 Assert.True(TestObject.Finalized);
186             }
187 
188             private class TestObject
189             {
190                 public static bool Finalized { get; private set; }
191 
~TestObject()192                 ~TestObject()
193                 {
194                     Finalized = true;
195                 }
196             }
197         }
198 
199         [Fact]
KeepAlive_Recursive()200         public static void KeepAlive_Recursive()
201         {
202             KeepAliveRecursiveTest.Run();
203         }
204 
205         private class KeepAliveRecursiveTest
206         {
Run()207             public static void Run()
208             {
209                 int recursionCount = 0;
210                 RunWorker(new TestObject(), ref recursionCount);
211             }
212 
RunWorker(object obj, ref int recursionCount)213             private static void RunWorker(object obj, ref int recursionCount)
214             {
215                 if (recursionCount++ == 10)
216                     return;
217 
218                 GC.Collect();
219                 GC.WaitForPendingFinalizers();
220 
221                 RunWorker(obj, ref recursionCount);
222 
223                 Assert.False(TestObject.Finalized);
224                 GC.KeepAlive(obj);
225             }
226 
227             private class TestObject
228             {
229                 public static bool Finalized { get; private set; }
230 
~TestObject()231                 ~TestObject()
232                 {
233                     Finalized = true;
234                 }
235             }
236         }
237 
238         [Fact]
SuppressFinalizer()239         public static void SuppressFinalizer()
240         {
241             SuppressFinalizerTest.Run();
242         }
243 
244         private class SuppressFinalizerTest
245         {
Run()246             public static void Run()
247             {
248                 var obj = new TestObject();
249                 GC.SuppressFinalize(obj);
250 
251                 obj = null;
252                 GC.Collect();
253                 GC.WaitForPendingFinalizers();
254 
255                 Assert.False(TestObject.Finalized);
256             }
257 
258             private class TestObject
259             {
260                 public static bool Finalized { get; private set; }
261 
~TestObject()262                 ~TestObject()
263                 {
264                     Finalized = true;
265                 }
266             }
267         }
268 
269         [Fact]
SuppressFinalizer_NullObject_ThrowsArgumentNullException()270         public static void SuppressFinalizer_NullObject_ThrowsArgumentNullException()
271         {
272             AssertExtensions.Throws<ArgumentNullException>("obj", () => GC.SuppressFinalize(null)); // Obj is null
273         }
274 
275         [Fact]
ReRegisterForFinalize()276         public static void ReRegisterForFinalize()
277         {
278             ReRegisterForFinalizeTest.Run();
279         }
280 
281         [Fact]
ReRegisterFoFinalize_NullObject_ThrowsArgumentNullException()282         public static void ReRegisterFoFinalize_NullObject_ThrowsArgumentNullException()
283         {
284             AssertExtensions.Throws<ArgumentNullException>("obj", () => GC.ReRegisterForFinalize(null)); // Obj is null
285         }
286 
287         private class ReRegisterForFinalizeTest
288         {
Run()289             public static void Run()
290             {
291                 TestObject.Finalized = false;
292                 CreateObject();
293 
294                 GC.Collect();
295                 GC.WaitForPendingFinalizers();
296 
297                 Assert.True(TestObject.Finalized);
298             }
299 
CreateObject()300             private static void CreateObject()
301             {
302                 using (var obj = new TestObject())
303                 {
304                     GC.SuppressFinalize(obj);
305                 }
306             }
307 
308             private class TestObject : IDisposable
309             {
310                 public static bool Finalized { get; set; }
311 
~TestObject()312                 ~TestObject()
313                 {
314                     Finalized = true;
315                 }
316 
Dispose()317                 public void Dispose()
318                 {
319                     GC.ReRegisterForFinalize(this);
320                 }
321             }
322         }
323 
324         [Fact]
CollectionCount_NegativeGeneration_ThrowsArgumentOutOfRangeException()325         public static void CollectionCount_NegativeGeneration_ThrowsArgumentOutOfRangeException()
326         {
327             AssertExtensions.Throws<ArgumentOutOfRangeException>("generation", () => GC.CollectionCount(-1)); // Generation < 0
328         }
329 
330         [Fact]
RemoveMemoryPressure_InvalidBytesAllocated_ThrowsArgumentOutOfRangeException()331         public static void RemoveMemoryPressure_InvalidBytesAllocated_ThrowsArgumentOutOfRangeException()
332         {
333             AssertExtensions.Throws<ArgumentOutOfRangeException>("bytesAllocated", () => GC.RemoveMemoryPressure(-1)); // Bytes allocated < 0
334 
335             if (s_is32Bits)
336             {
337                 AssertExtensions.Throws<ArgumentOutOfRangeException>("bytesAllocated", () => GC.RemoveMemoryPressure((long)int.MaxValue + 1)); // Bytes allocated > int.MaxValue on 32 bit platforms
338             }
339         }
340 
341         [Fact]
GetTotalMemoryTest_ForceCollection()342         public static void GetTotalMemoryTest_ForceCollection()
343         {
344             // We don't test GetTotalMemory(false) at all because a collection
345             // could still occur even if not due to the GetTotalMemory call,
346             // and as such there's no way to validate the behavior.  We also
347             // don't verify a tighter bound for the result of GetTotalMemory
348             // because collections could cause significant fluctuations.
349 
350             GC.Collect();
351 
352             int gen0 = GC.CollectionCount(0);
353             int gen1 = GC.CollectionCount(1);
354             int gen2 = GC.CollectionCount(2);
355 
356             Assert.InRange(GC.GetTotalMemory(true), 1, long.MaxValue);
357 
358             Assert.InRange(GC.CollectionCount(0), gen0 + 1, int.MaxValue);
359             Assert.InRange(GC.CollectionCount(1), gen1 + 1, int.MaxValue);
360             Assert.InRange(GC.CollectionCount(2), gen2 + 1, int.MaxValue);
361         }
362 
363         [Fact]
GetGeneration()364         public static void GetGeneration()
365         {
366             // We don't test a tighter bound on GetGeneration as objects
367             // can actually get demoted or stay in the same generation
368             // across collections.
369 
370             GC.Collect();
371             var obj = new object();
372 
373             for (int i = 0; i <= GC.MaxGeneration + 1; i++)
374             {
375                 Assert.InRange(GC.GetGeneration(obj), 0, GC.MaxGeneration);
376                 GC.Collect();
377             }
378         }
379 
380         [Theory]
381         [InlineData(GCLargeObjectHeapCompactionMode.CompactOnce)]
382         [InlineData(GCLargeObjectHeapCompactionMode.Default)]
LargeObjectHeapCompactionModeRoundTrips(GCLargeObjectHeapCompactionMode value)383         public static void LargeObjectHeapCompactionModeRoundTrips(GCLargeObjectHeapCompactionMode value)
384         {
385             GCLargeObjectHeapCompactionMode orig = GCSettings.LargeObjectHeapCompactionMode;
386             try
387             {
388                 GCSettings.LargeObjectHeapCompactionMode = value;
389                 Assert.Equal(value, GCSettings.LargeObjectHeapCompactionMode);
390             }
391             finally
392             {
393                 GCSettings.LargeObjectHeapCompactionMode = orig;
394                 Assert.Equal(orig, GCSettings.LargeObjectHeapCompactionMode);
395             }
396         }
397 
398         [Theory]
399         [InlineData(GCLatencyMode.Batch)]
400         [InlineData(GCLatencyMode.Interactive)]
LatencyRoundtrips(GCLatencyMode value)401         public static void LatencyRoundtrips(GCLatencyMode value)
402         {
403             GCLatencyMode orig = GCSettings.LatencyMode;
404             try
405             {
406                 GCSettings.LatencyMode = value;
407                 Assert.Equal(value, GCSettings.LatencyMode);
408             }
409             finally
410             {
411                 GCSettings.LatencyMode = orig;
412                 Assert.Equal(orig, GCSettings.LatencyMode);
413             }
414         }
415 
416         [Theory]
417         [PlatformSpecific(TestPlatforms.Windows)] //Concurrent GC is not enabled on Unix. Recombine to TestLatencyRoundTrips once addressed.
418         [InlineData(GCLatencyMode.LowLatency)]
419         [InlineData(GCLatencyMode.SustainedLowLatency)]
420         public static void LatencyRoundtrips_LowLatency(GCLatencyMode value) => LatencyRoundtrips(value);
421     }
422 
423     public class GCExtendedTests : RemoteExecutorTestBase
424     {
425         private const int TimeoutMilliseconds = 10 * 30 * 1000; //if full GC is triggered it may take a while
426 
427         /// <summary>
428         /// NoGC regions will be automatically exited if more than the requested budget
429         /// is allocated while still in the region. In order to avoid this, the budget is set
430         /// to be higher than what the test should be allocating. When running on CoreCLR/DesktopCLR,
431         /// these tests generally do not allocate because they are implemented as fcalls into the runtime
432         /// itself, but the CoreRT runtime is written in mostly managed code and tends to allocate more.
433         ///
434         /// This budget should be high enough to avoid exiting no-gc regions when doing normal unit
435         /// tests, regardless of the runtime.
436         /// </summary>
437         private const int NoGCRequestedBudget = 8192;
438 
439         [Fact]
440         [OuterLoop]
GetGeneration_WeakReference()441         public static void GetGeneration_WeakReference()
442         {
443             RemoteInvokeOptions options = new RemoteInvokeOptions();
444             options.TimeOut = TimeoutMilliseconds;
445             RemoteInvoke(() =>
446                 {
447 
448                     Func<WeakReference> getweakref = delegate ()
449                     {
450                         Version myobj = new Version();
451                         var wkref = new WeakReference(myobj);
452 
453                         Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget));
454                         Assert.True(GC.GetGeneration(wkref) >= 0);
455                         Assert.Equal(GC.GetGeneration(wkref), GC.GetGeneration(myobj));
456                         GC.EndNoGCRegion();
457 
458                         myobj = null;
459                         return wkref;
460                     };
461 
462                     WeakReference weakref = getweakref();
463                     Assert.True(weakref != null);
464 #if !DEBUG
465                     GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true);
466                     Assert.Throws<ArgumentNullException>(() => GC.GetGeneration(weakref));
467 #endif
468                     return SuccessExitCode;
469                 }, options).Dispose();
470 
471         }
472 
473         [Fact]
GCNotificationNegTests()474         public static void GCNotificationNegTests()
475         {
476             Assert.Throws<ArgumentOutOfRangeException>(() => GC.RegisterForFullGCNotification(-1, -1));
477             Assert.Throws<ArgumentOutOfRangeException>(() => GC.RegisterForFullGCNotification(100, -1));
478             Assert.Throws<ArgumentOutOfRangeException>(() => GC.RegisterForFullGCNotification(-1, 100));
479 
480             Assert.Throws<ArgumentOutOfRangeException>(() => GC.RegisterForFullGCNotification(10, -1));
481             Assert.Throws<ArgumentOutOfRangeException>(() => GC.RegisterForFullGCNotification(-1, 10));
482             Assert.Throws<ArgumentOutOfRangeException>(() => GC.RegisterForFullGCNotification(100, 10));
483             Assert.Throws<ArgumentOutOfRangeException>(() => GC.RegisterForFullGCNotification(10, 100));
484 
485 
486             Assert.Throws<ArgumentOutOfRangeException>(() => GC.WaitForFullGCApproach(-2));
487             Assert.Throws<ArgumentOutOfRangeException>(() => GC.WaitForFullGCComplete(-2));
488         }
489 
490         [Theory]
491         [InlineData(true, -1)]
492         [InlineData(false, -1)]
493         [InlineData(true, 0)]
494         [InlineData(false, 0)]
495         [InlineData(true, 100)]
496         [InlineData(false, 100)]
497         [InlineData(true, int.MaxValue)]
498         [InlineData(false, int.MaxValue)]
499         [OuterLoop]
GCNotificationTests(bool approach, int timeout)500         public static void GCNotificationTests(bool approach, int timeout)
501         {
502             RemoteInvokeOptions options = new RemoteInvokeOptions();
503             options.TimeOut = TimeoutMilliseconds;
504             RemoteInvoke((approachString, timeoutString) =>
505                 {
506                     TestWait(bool.Parse(approachString), int.Parse(timeoutString));
507                     return SuccessExitCode;
508                 }, approach.ToString(), timeout.ToString(), options).Dispose();
509         }
510 
511         [Fact]
512         [OuterLoop]
TryStartNoGCRegion_EndNoGCRegion_ThrowsInvalidOperationException()513         public static void TryStartNoGCRegion_EndNoGCRegion_ThrowsInvalidOperationException()
514         {
515             RemoteInvokeOptions options = new RemoteInvokeOptions();
516             options.TimeOut = TimeoutMilliseconds;
517             RemoteInvoke(() =>
518                 {
519                     Assert.Throws<InvalidOperationException>(() => GC.EndNoGCRegion());
520                     return SuccessExitCode;
521                 }, options).Dispose();
522         }
523 
524         [MethodImpl(MethodImplOptions.NoOptimization)]
AllocateALot()525         private static void AllocateALot()
526         {
527             for (int i = 0; i < 10000; i++)
528             {
529                 var array = new long[NoGCRequestedBudget];
530                 GC.KeepAlive(array);
531             }
532         }
533 
534         [Fact]
535         [OuterLoop]
TryStartNoGCRegion_ExitThroughAllocation()536         public static void TryStartNoGCRegion_ExitThroughAllocation()
537         {
538             RemoteInvokeOptions options = new RemoteInvokeOptions();
539             options.TimeOut = TimeoutMilliseconds;
540             RemoteInvoke(() =>
541                 {
542                     Assert.True(GC.TryStartNoGCRegion(1024));
543 
544                     AllocateALot();
545 
546                     // at this point, the GC should have booted us out of the no GC region
547                     // since we allocated too much.
548                     Assert.Throws<InvalidOperationException>(() => GC.EndNoGCRegion());
549                     return SuccessExitCode;
550                 }, options).Dispose();
551         }
552 
553         [Fact]
554         [OuterLoop]
TryStartNoGCRegion_StartWhileInNoGCRegion()555         public static void TryStartNoGCRegion_StartWhileInNoGCRegion()
556         {
557             RemoteInvokeOptions options = new RemoteInvokeOptions();
558             options.TimeOut = TimeoutMilliseconds;
559             RemoteInvoke(() =>
560             {
561                 Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget));
562                 Assert.Throws<InvalidOperationException>(() => GC.TryStartNoGCRegion(NoGCRequestedBudget));
563 
564                 Assert.Throws<InvalidOperationException>(() => GC.EndNoGCRegion());
565 
566                 return SuccessExitCode;
567             }, options).Dispose();
568         }
569 
570         [Fact]
571         [OuterLoop]
TryStartNoGCRegion_StartWhileInNoGCRegion_BlockingCollection()572         public static void TryStartNoGCRegion_StartWhileInNoGCRegion_BlockingCollection()
573         {
574             RemoteInvokeOptions options = new RemoteInvokeOptions();
575             options.TimeOut = TimeoutMilliseconds;
576             RemoteInvoke(() =>
577             {
578                 Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget, true));
579                 Assert.Throws<InvalidOperationException>(() => GC.TryStartNoGCRegion(NoGCRequestedBudget, true));
580 
581                 Assert.Throws<InvalidOperationException>(() => GC.EndNoGCRegion());
582 
583                 return SuccessExitCode;
584             }, options).Dispose();
585         }
586 
587         [Fact]
588         [OuterLoop]
TryStartNoGCRegion_StartWhileInNoGCRegion_LargeObjectHeapSize()589         public static void TryStartNoGCRegion_StartWhileInNoGCRegion_LargeObjectHeapSize()
590         {
591             RemoteInvokeOptions options = new RemoteInvokeOptions();
592             options.TimeOut = TimeoutMilliseconds;
593             RemoteInvoke(() =>
594             {
595                 Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget, NoGCRequestedBudget));
596                 Assert.Throws<InvalidOperationException>(() => GC.TryStartNoGCRegion(NoGCRequestedBudget, NoGCRequestedBudget));
597 
598                 Assert.Throws<InvalidOperationException>(() => GC.EndNoGCRegion());
599 
600                 return SuccessExitCode;
601             }, options).Dispose();
602         }
603 
604         [Fact]
605         [OuterLoop]
TryStartNoGCRegion_StartWhileInNoGCRegion_BlockingCollectionAndLOH()606         public static void TryStartNoGCRegion_StartWhileInNoGCRegion_BlockingCollectionAndLOH()
607         {
608             RemoteInvokeOptions options = new RemoteInvokeOptions();
609             options.TimeOut = TimeoutMilliseconds;
610             RemoteInvoke(() =>
611             {
612                 Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget, NoGCRequestedBudget, true));
613                 Assert.Throws<InvalidOperationException>(() => GC.TryStartNoGCRegion(NoGCRequestedBudget, NoGCRequestedBudget, true));
614 
615                 Assert.Throws<InvalidOperationException>(() => GC.EndNoGCRegion());
616 
617                 return SuccessExitCode;
618             }, options).Dispose();
619         }
620 
621         [Fact]
622         [OuterLoop]
TryStartNoGCRegion_SettingLatencyMode_ThrowsInvalidOperationException()623         public static void TryStartNoGCRegion_SettingLatencyMode_ThrowsInvalidOperationException()
624         {
625             RemoteInvokeOptions options = new RemoteInvokeOptions();
626             options.TimeOut = TimeoutMilliseconds;
627             RemoteInvoke(() =>
628             {
629                 // The budget for this test is 4mb, because the act of throwing an exception with a message
630                 // contained in a resource file has to potential to allocate a lot on CoreRT. In particular, when compiling
631                 // in multi-file mode, this will trigger a resource lookup in System.Private.CoreLib.
632                 //
633                 // In addition to this, the Assert.Throws xunit combinator tends to also allocate a lot.
634                 Assert.True(GC.TryStartNoGCRegion(4000 * 1024, true));
635                 Assert.Equal(GCSettings.LatencyMode, GCLatencyMode.NoGCRegion);
636                 Assert.Throws<InvalidOperationException>(() => GCSettings.LatencyMode = GCLatencyMode.LowLatency);
637 
638                 GC.EndNoGCRegion();
639 
640                 return SuccessExitCode;
641             }, options).Dispose();
642         }
643 
644         [Fact]
645         [OuterLoop]
TryStartNoGCRegion_SOHSize()646         public static void TryStartNoGCRegion_SOHSize()
647         {
648             RemoteInvokeOptions options = new RemoteInvokeOptions();
649             options.TimeOut = TimeoutMilliseconds;
650             RemoteInvoke(() =>
651                 {
652 
653                     Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget));
654                     Assert.Equal(GCSettings.LatencyMode, GCLatencyMode.NoGCRegion);
655                     GC.EndNoGCRegion();
656 
657                     return SuccessExitCode;
658 
659                 }, options).Dispose();
660         }
661 
662         [Fact]
663         [OuterLoop]
TryStartNoGCRegion_SOHSize_BlockingCollection()664         public static void TryStartNoGCRegion_SOHSize_BlockingCollection()
665         {
666             RemoteInvokeOptions options = new RemoteInvokeOptions();
667             options.TimeOut = TimeoutMilliseconds;
668             RemoteInvoke(() =>
669             {
670                 Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget, true));
671                 Assert.Equal(GCSettings.LatencyMode, GCLatencyMode.NoGCRegion);
672                 GC.EndNoGCRegion();
673 
674                 return SuccessExitCode;
675 
676             }, options).Dispose();
677         }
678 
679         [Fact]
680         [OuterLoop]
TryStartNoGCRegion_SOHSize_LOHSize()681         public static void TryStartNoGCRegion_SOHSize_LOHSize()
682         {
683             RemoteInvokeOptions options = new RemoteInvokeOptions();
684             options.TimeOut = TimeoutMilliseconds;
685             RemoteInvoke(() =>
686             {
687                 Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget, NoGCRequestedBudget));
688                 Assert.Equal(GCSettings.LatencyMode, GCLatencyMode.NoGCRegion);
689                 GC.EndNoGCRegion();
690 
691                 return SuccessExitCode;
692 
693             }, options).Dispose();
694         }
695 
696         [Fact]
697         [OuterLoop]
TryStartNoGCRegion_SOHSize_LOHSize_BlockingCollection()698         public static void TryStartNoGCRegion_SOHSize_LOHSize_BlockingCollection()
699         {
700             RemoteInvokeOptions options = new RemoteInvokeOptions();
701             options.TimeOut = TimeoutMilliseconds;
702             RemoteInvoke(() =>
703             {
704                 Assert.True(GC.TryStartNoGCRegion(NoGCRequestedBudget, NoGCRequestedBudget, true));
705                 Assert.Equal(GCSettings.LatencyMode, GCLatencyMode.NoGCRegion);
706                 GC.EndNoGCRegion();
707 
708                 return SuccessExitCode;
709 
710             }, options).Dispose();
711         }
712 
713         [Theory]
714         [OuterLoop]
715         [InlineData(0)]
716         [InlineData(-1)]
717         [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Difference in behavior, full framework doesn't throw, fixed in .NET Core")]
TryStartNoGCRegion_TotalSizeOutOfRange(long size)718         public static void TryStartNoGCRegion_TotalSizeOutOfRange(long size)
719         {
720             RemoteInvokeOptions options = new RemoteInvokeOptions();
721             options.TimeOut = TimeoutMilliseconds;
722             RemoteInvoke(sizeString =>
723             {
724                 AssertExtensions.Throws<ArgumentOutOfRangeException>("totalSize", () => GC.TryStartNoGCRegion(long.Parse(sizeString)));
725                 return SuccessExitCode;
726             }, size.ToString(), options).Dispose();
727         }
728 
729         [Theory]
730         [OuterLoop]
731         [InlineData(0)]                   // invalid because lohSize ==
732         [InlineData(-1)]                  // invalid because lohSize < 0
733         [InlineData(1152921504606846976)] // invalid because lohSize > totalSize
734         [SkipOnTargetFramework(TargetFrameworkMonikers.NetFramework, "Difference in behavior, full framework doesn't throw, fixed in .NET Core")]
TryStartNoGCRegion_LOHSizeInvalid(long size)735         public static void TryStartNoGCRegion_LOHSizeInvalid(long size)
736         {
737             RemoteInvokeOptions options = new RemoteInvokeOptions();
738             options.TimeOut = TimeoutMilliseconds;
739             RemoteInvoke(sizeString =>
740             {
741                 AssertExtensions.Throws<ArgumentOutOfRangeException>("lohSize", () => GC.TryStartNoGCRegion(1024, long.Parse(sizeString)));
742                 return SuccessExitCode;
743             }, size.ToString(), options).Dispose();
744         }
745 
TestWait(bool approach, int timeout)746         public static void TestWait(bool approach, int timeout)
747         {
748             GCNotificationStatus result = GCNotificationStatus.Failed;
749             Thread cancelProc = null;
750 
751             // Since we need to test an infinite (or very large) wait but the API won't return, spawn off a thread which
752             // will cancel the wait after a few seconds
753             //
754             bool cancelTimeout = (timeout == -1) || (timeout > 10000);
755 
756             GC.RegisterForFullGCNotification(20, 20);
757 
758             try
759             {
760                 if (cancelTimeout)
761                 {
762                     cancelProc = new Thread(new ThreadStart(CancelProc));
763                     cancelProc.Start();
764                 }
765 
766                 if (approach)
767                     result = GC.WaitForFullGCApproach(timeout);
768                 else
769                     result = GC.WaitForFullGCComplete(timeout);
770             }
771             catch (Exception e)
772             {
773                 Assert.True(false, $"({approach}, {timeout}) Error - Unexpected exception received: {e.ToString()}");
774             }
775             finally
776             {
777                 if (cancelProc != null)
778                     cancelProc.Join();
779             }
780 
781             if (cancelTimeout)
782             {
783                 Assert.True(result == GCNotificationStatus.Canceled, $"({approach}, {timeout}) Error - WaitForFullGCApproach result not Cancelled");
784             }
785             else
786             {
787                 Assert.True(result == GCNotificationStatus.Timeout, $"({approach}, {timeout}) Error - WaitForFullGCApproach result not Timeout");
788             }
789         }
790 
CancelProc()791         public static void CancelProc()
792         {
793             Thread.Sleep(500);
794             GC.CancelFullGCNotification();
795         }
796     }
797 }
798