|
|
@@ -0,0 +1,65 @@
|
|
|
+namespace Jint.Tests.Runtime;
|
|
|
+
|
|
|
+// This needs to run without any parallelization because it uses
|
|
|
+// garbage collector metrics which cannot be isolated.
|
|
|
+[CollectionDefinition(nameof(GarbageCollectionTests), DisableParallelization = true)]
|
|
|
+[Collection(nameof(GarbageCollectionTests))]
|
|
|
+public class GarbageCollectionTests
|
|
|
+{
|
|
|
+ [Fact]
|
|
|
+ public void InternalCachingDoesNotPreventGarbageCollection()
|
|
|
+ {
|
|
|
+ // This test ensures that memory allocated within functions
|
|
|
+ // can be garbage collected by the .NET runtime. To test that,
|
|
|
+ // the "allocate" functions allocates a big chunk of memory,
|
|
|
+ // which is not used anywhere. So the GC should have no problem
|
|
|
+ // releasing that memory after the "allocate" function leaves.
|
|
|
+
|
|
|
+ // Arrange
|
|
|
+ var engine = new Engine();
|
|
|
+ const string script =
|
|
|
+ """
|
|
|
+ function allocate(runAllocation) {
|
|
|
+ if (runAllocation) {
|
|
|
+ // Allocate ~200 MB of data (not 100 because .NET uses UTF-16 for strings)
|
|
|
+ var test = Array.from({ length: 100 })
|
|
|
+ .map(() => ' '.repeat(1 * 1024 * 1024));
|
|
|
+ }
|
|
|
+ return 2;
|
|
|
+ }
|
|
|
+ """;
|
|
|
+ engine.Evaluate(script);
|
|
|
+
|
|
|
+ // Create a baseline for memory usage.
|
|
|
+ engine.Evaluate("allocate(false);");
|
|
|
+ var usedMemoryBytesBaseline = CurrentlyUsedMemory();
|
|
|
+
|
|
|
+ // Act
|
|
|
+ engine.Evaluate("allocate(true);");
|
|
|
+
|
|
|
+ // Assert
|
|
|
+ var usedMemoryBytesAfterJsScript = CurrentlyUsedMemory();
|
|
|
+ var epsilon = 10 * 1024 * 1024; // allowing up to 10 MB of other allocations should be enough to prevent false positives
|
|
|
+ Assert.True(
|
|
|
+ usedMemoryBytesAfterJsScript - usedMemoryBytesBaseline < epsilon,
|
|
|
+ userMessage: $"""
|
|
|
+ The garbage collector did not free the allocated but unreachable 200 MB from the script.;
|
|
|
+ Before Call : {BytesToString(usedMemoryBytesBaseline)}
|
|
|
+ After Call : {BytesToString(usedMemoryBytesAfterJsScript)}
|
|
|
+ ---
|
|
|
+ Acceptable : {BytesToString(usedMemoryBytesBaseline + epsilon)}
|
|
|
+ """);
|
|
|
+ return;
|
|
|
+
|
|
|
+ static string BytesToString(long bytes)
|
|
|
+ => $"{(bytes / 1024.0 / 1024.0),6:0.0} MB";
|
|
|
+
|
|
|
+ static long CurrentlyUsedMemory()
|
|
|
+ {
|
|
|
+ // Just try to ensure that everything possible gets collected.
|
|
|
+ GC.Collect(2, GCCollectionMode.Forced, blocking: true);
|
|
|
+ var currentlyUsed = GC.GetTotalMemory(forceFullCollection: true);
|
|
|
+ return currentlyUsed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|