GarbageCollectionTests.cs 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. namespace Jint.Tests.Runtime;
  2. // This needs to run without any parallelization because it uses
  3. // garbage collector metrics which cannot be isolated.
  4. [CollectionDefinition(nameof(GarbageCollectionTests), DisableParallelization = true)]
  5. [Collection(nameof(GarbageCollectionTests))]
  6. public class GarbageCollectionTests
  7. {
  8. [Fact]
  9. public void InternalCachingDoesNotPreventGarbageCollection()
  10. {
  11. // This test ensures that memory allocated within functions
  12. // can be garbage collected by the .NET runtime. To test that,
  13. // the "allocate" functions allocates a big chunk of memory,
  14. // which is not used anywhere. So the GC should have no problem
  15. // releasing that memory after the "allocate" function leaves.
  16. // Arrange
  17. var engine = new Engine();
  18. const string script =
  19. """
  20. function allocate(runAllocation) {
  21. if (runAllocation) {
  22. // Allocate ~200 MB of data (not 100 because .NET uses UTF-16 for strings)
  23. var test = Array.from({ length: 100 })
  24. .map(() => ' '.repeat(1 * 1024 * 1024));
  25. }
  26. return 2;
  27. }
  28. """;
  29. engine.Evaluate(script);
  30. // Create a baseline for memory usage.
  31. engine.Evaluate("allocate(false);");
  32. var usedMemoryBytesBaseline = CurrentlyUsedMemory();
  33. // Act
  34. engine.Evaluate("allocate(true);");
  35. // Assert
  36. var usedMemoryBytesAfterJsScript = CurrentlyUsedMemory();
  37. var epsilon = 10 * 1024 * 1024; // allowing up to 10 MB of other allocations should be enough to prevent false positives
  38. Assert.True(
  39. usedMemoryBytesAfterJsScript - usedMemoryBytesBaseline < epsilon,
  40. userMessage: $"""
  41. The garbage collector did not free the allocated but unreachable 200 MB from the script.;
  42. Before Call : {BytesToString(usedMemoryBytesBaseline)}
  43. After Call : {BytesToString(usedMemoryBytesAfterJsScript)}
  44. ---
  45. Acceptable : {BytesToString(usedMemoryBytesBaseline + epsilon)}
  46. """);
  47. return;
  48. static string BytesToString(long bytes)
  49. => $"{(bytes / 1024.0 / 1024.0),6:0.0} MB";
  50. static long CurrentlyUsedMemory()
  51. {
  52. // Just try to ensure that everything possible gets collected.
  53. GC.Collect(2, GCCollectionMode.Forced, blocking: true);
  54. var currentlyUsed = GC.GetTotalMemory(forceFullCollection: true);
  55. return currentlyUsed;
  56. }
  57. }
  58. }