forked from microsoft/node-api-dotnet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGCTests.cs
More file actions
134 lines (107 loc) · 4.75 KB
/
GCTests.cs
File metadata and controls
134 lines (107 loc) · 4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.JavaScript.NodeApi.Interop;
using Microsoft.JavaScript.NodeApi.Runtime;
using Xunit;
using static Microsoft.JavaScript.NodeApi.Test.TestUtils;
namespace Microsoft.JavaScript.NodeApi.Test;
public class GCTests
{
[Fact]
public void GCHandles()
{
using NodeEmbeddingThreadRuntime nodejs =
NodejsEmbeddingTests.CreateNodeEmbeddingThreadRuntime();
nodejs.Run(() =>
{
// 3 GC handles are created in the NodeEmbeddingThreadRuntime constructor
// to define the 'require', 'resolve', and ' import' functions.
Assert.Equal(3, JSRuntimeContext.Current.GCHandleCount);
JSClassBuilder<DotnetClass> classBuilder =
new(nameof(DotnetClass), () => new DotnetClass());
classBuilder.AddProperty(
"property",
(x) => x.Property,
(x, value) => x.Property = (string)value);
classBuilder.AddMethod("method", (x) => (args) => x.Method());
JSObject dotnetClass = (JSObject)classBuilder.DefineClass();
JSFunction jsCreateInstanceFunction = (JSFunction)JSValue.RunScript(
"function jsCreateInstanceFunction(Class) { new Class() }; " +
"jsCreateInstanceFunction");
// 5 GC handles are expected
// - Type: DotnetClass
// - JSCallback: DotnetClass.constructor
// - JSPropertyDescriptor: DotnetClass.property
// - JSPropertyDescriptor: DotnetClass.method
// - JSPropertyDescriptor: DotnetClass.toString
Assert.Equal(3 + 5, JSRuntimeContext.Current.GCHandleCount);
using JSValueScope innerScope = new(JSValueScopeType.Callback);
jsCreateInstanceFunction.CallAsStatic(dotnetClass);
// Two more handles should have been allocated by the JS create-instance function call.
// - One for the 'external' type value passed to the constructor.
// - One for the JS object wrapper.
Assert.Equal(3 + 5 + 2, JSRuntimeContext.Current.GCHandleCount);
});
nodejs.GC();
nodejs.Run(() =>
{
// After GC, the handle count should have reverted back to the original set.
Assert.Equal(3 + 5, JSRuntimeContext.Current.GCHandleCount);
});
}
[Fact]
public void GCObjects()
{
using NodeEmbeddingThreadRuntime nodejs =
NodejsEmbeddingTests.CreateNodeEmbeddingThreadRuntime();
nodejs.Run(() =>
{
JSClassBuilder<DotnetClass> classBuilder =
new(nameof(DotnetClass), () => new DotnetClass());
classBuilder.AddProperty(
"property",
(x) => x.Property,
(x, value) => x.Property = (string)value);
classBuilder.AddMethod("method", (x) => (args) => x.Method());
JSObject dotnetClass = (JSObject)classBuilder.DefineClass();
JSFunction jsCreateInstanceFunction = (JSFunction)JSValue.RunScript(
"function jsCreateInstanceFunction(Class) { new Class() }; " +
"jsCreateInstanceFunction");
Assert.Equal(8, JSRuntimeContext.Current.GCHandleCount);
using (JSValueScope innerScope = new(JSValueScopeType.Callback))
{
jsCreateInstanceFunction.CallAsStatic(dotnetClass);
}
});
// One .NET object instance was created by the JS function.
Assert.Equal(1ul, DotnetClass.Instances);
// Request a JS GC, which should release the JS object referencing the .NET object.
// Pump the Node event loop with an empty Run() callback to complete the GC.
nodejs.GC();
nodejs.Run(() => { });
// The JS object released its reference to the .NET object, but it hasn't been GC'd yet.
Assert.Equal(1ul, DotnetClass.Instances);
// Request a .NET GC, and wait for finalizers (which run on another thread after the GC).
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
// Now the .NET object should have been finalized/GC'd, as indicated by the
// instance count decremented by the finalizer.
Assert.Equal(0ul, DotnetClass.Instances);
}
private class DotnetClass
{
public static ulong Instances;
public DotnetClass()
{
++Instances;
}
public string Property { get; set; } = string.Empty;
#pragma warning disable CA1822 // Method does not access instance data and can be marked as static
public void Method() { }
#pragma warning restore CA1822
~DotnetClass()
{
--Instances;
}
}
}