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