Skip to content

Commit b104956

Browse files
committed
Work on GC APIs
- Add APIs previsouly in Native.Runtime assembly. - Add `Collect` and `GetTotalMemory` to follow .NET API. - `Run` is now private and used in both the new APIs. - Improve IntelliSense comments. - Rework GC unit tests to take advantage of the new APIs in GC. - Add new Unit test to cover `GetTotalMemory`.
1 parent 40ce642 commit b104956

File tree

3 files changed

+121
-70
lines changed

3 files changed

+121
-70
lines changed

Tests/NFUnitTestGC/TestGC.cs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44

5+
using System;
56
using nanoFramework.TestFramework;
67

78
namespace NFUnitTestGC
@@ -15,16 +16,14 @@ public void TestGCStress()
1516
int maxArraySize = 1024 * 32;
1617
object[] arrays = new object[600];
1718

18-
// Starting TestGCStress
19-
2019
for (int loop = 0; loop < 100; loop++)
2120
{
2221
OutputHelper.WriteLine($"Running iteration {loop}");
2322

2423
for (int i = 0; i < arrays.Length - 1;)
2524
{
2625
OutputHelper.WriteLine($"Alloc array of {maxArraySize} bytes @ pos {i}");
27-
arrays[i++] = new byte[maxArraySize]; ;
26+
arrays[i++] = new byte[maxArraySize];
2827

2928
OutputHelper.WriteLine($"Alloc array of 64 bytes @ pos {i}");
3029
arrays[i++] = new byte[64];
@@ -37,8 +36,35 @@ public void TestGCStress()
3736
arrays[i] = null;
3837
}
3938
}
39+
}
40+
41+
[TestMethod]
42+
public void TestGetTotalMemory()
43+
{
44+
// create several objects
45+
object[] objects = new object[100];
46+
47+
for (int i = 0; i < objects.Length; i++)
48+
{
49+
objects[i] = new object();
50+
}
51+
52+
// get total memory
53+
long totalMemory = GC.GetTotalMemory(false);
54+
OutputHelper.WriteLine($"Total memory: {totalMemory} bytes");
55+
56+
// release objects
57+
for (int i = 0; i < objects.Length; i++)
58+
{
59+
objects[i] = null;
60+
}
61+
62+
// get total memory, forcing full collection
63+
long totalMemoryAfterCollection = GC.GetTotalMemory(true);
64+
OutputHelper.WriteLine($"Total memory: {totalMemoryAfterCollection} bytes");
4065

41-
// Completed TestGCStress
66+
// check if memory was released
67+
Assert.IsTrue(totalMemory > totalMemoryAfterCollection, "Memory was not released");
4268
}
4369
}
4470
}

Tests/NFUnitTestSystemLib/UnitTestGCTest.cs

Lines changed: 35 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ namespace NFUnitTestSystemLib
99
[TestClass]
1010
public class UnitTestGCTest
1111
{
12+
#pragma warning disable S1215 // this is intended to test the GC
13+
#pragma warning disable S1854 // this is intended to test the GC
14+
#pragma warning disable S2696 // this is intended to test the GC
15+
#pragma warning disable S3971 // this is intended to test the GC
16+
1217
internal class FinalizeObject
1318
{
1419
public static FinalizeObject m_currentInstance = null;
@@ -54,17 +59,20 @@ public void SystemGC1_Test()
5459
/// 6. Verify that object has been collected
5560
/// </summary>
5661
///
57-
// Tests ReRegisterForFinalize
58-
// Create a FinalizeObject.
62+
63+
OutputHelper.WriteLine("Tests ReRegisterForFinalize");
64+
OutputHelper.WriteLine("Create a FinalizeObject.");
65+
5966
FinalizeObject mfo = new FinalizeObject();
6067
m_hasFinalized1 = false;
6168
m_hasFinalized2 = false;
6269

6370
// Release reference
71+
OutputHelper.WriteLine("Release reference");
6472
mfo = null;
6573

66-
// Allow GC
67-
GC.WaitForPendingFinalizers();
74+
OutputHelper.WriteLine("Allow GC");
75+
GC.Collect();
6876

6977
int sleepTime = 1000;
7078
int slept = 0;
@@ -85,10 +93,10 @@ public void SystemGC1_Test()
8593
// FinalizeObject.m_currentInstance field. Setting this value
8694
// to null and forcing another garbage collection will now
8795
// cause the object to Finalize permanently.
88-
// Reregister and allow for GC
89-
FinalizeObject.m_currentInstance = null;
9096

91-
GC.WaitForPendingFinalizers();
97+
OutputHelper.WriteLine("Reregister and allow for GC");
98+
FinalizeObject.m_currentInstance = null;
99+
GC.Collect();
92100

93101
sleepTime = 1000;
94102
slept = 0;
@@ -119,26 +127,27 @@ public void SystemGC2_Test()
119127
/// 6. Verify that object has not been collected
120128
/// </summary>
121129
///
122-
// Tests SuppressFinalize
123-
// Create a FinalizeObject.
130+
131+
OutputHelper.WriteLine("Tests SuppressFinalize");
132+
OutputHelper.WriteLine("Create a FinalizeObject");
124133
FinalizeObject mfo = new FinalizeObject();
125134
m_hasFinalized1 = false;
126135
m_hasFinalized2 = false;
127136

128-
// Releasing
137+
OutputHelper.WriteLine("Releasing");
129138
GC.SuppressFinalize(mfo);
130139
mfo = null;
131140

132-
// Allow GC
133-
GC.WaitForPendingFinalizers();
141+
OutputHelper.WriteLine("Allow GC");
142+
GC.Collect();
134143

135144
int sleepTime = 1000;
136145
int slept = 0;
137146

138147
while (!m_hasFinalized1 && slept < sleepTime)
139148
{
140149
// force GC run caused by memory allocation
141-
var dummyArray = new byte[1024 * 1024 * 1];
150+
_ = new byte[1024 * 1024 * 1];
142151

143152
System.Threading.Thread.Sleep(10);
144153
slept += 10;
@@ -161,59 +170,35 @@ public void SystemGC3_Test()
161170
/// </summary>
162171
///
163172

164-
// Tests WaitForPendingFinalizers, dependant on test 1
165-
// will auto-fail if test 1 fails.
166173
OutputHelper.Write("Tests WaitForPendingFinalizers, dependant on test 1");
167-
OutputHelper.WriteLine("will auto-fail if test 1 fails.");
174+
OutputHelper.WriteLine("will fail if test 1 fails.");
168175

169-
Assert.IsTrue(m_Test1Result);
176+
Assert.IsTrue(m_Test1Result, "Can't run this test as SystemGC1_Test has failed.");
170177

171-
// Create a FinalizeObject.
178+
OutputHelper.WriteLine("Create a FinalizeObject");
172179
FinalizeObject mfo = new FinalizeObject();
173180
m_hasFinalized1 = false;
174181
m_hasFinalized2 = false;
175182

176-
// Releasing
183+
OutputHelper.WriteLine("Releasing");
177184
mfo = null;
178185

179-
int sleepTime = 1000;
180-
int slept = 0;
181-
182-
while (!m_hasFinalized1 && slept < sleepTime)
183-
{
184-
// force GC run caused by memory allocation
185-
var dummyArray = new byte[1024 * 1024 * 1];
186-
187-
System.Threading.Thread.Sleep(10);
188-
slept += 10;
189-
}
190-
191-
OutputHelper.WriteLine($"GC took {slept}");
192-
193-
// Wait for GC
186+
OutputHelper.WriteLine("Wait for GC");
187+
GC.Collect();
194188
GC.WaitForPendingFinalizers();
195189

196-
// Releasing again
190+
OutputHelper.WriteLine("Releasing again");
197191
FinalizeObject.m_currentInstance = null;
198192

199-
sleepTime = 1000;
200-
slept = 0;
201-
202-
while (!m_hasFinalized2 && slept < sleepTime)
203-
{
204-
// force GC run caused by memory allocation
205-
var dummyArray = new byte[1024 * 1024 * 1];
206-
207-
System.Threading.Thread.Sleep(10);
208-
slept += 10;
209-
}
210-
211-
OutputHelper.WriteLine($"GC took {slept}");
212-
213-
// Wait for GC
193+
OutputHelper.WriteLine("Wait for GC");
194+
GC.Collect();
214195
GC.WaitForPendingFinalizers();
215196

216197
Assert.IsTrue(m_hasFinalized2);
217198
}
218199
}
200+
#pragma warning restore S1215 // "GC.Collect" should not be called
201+
#pragma warning restore S1854 // Unused assignments should be removed
202+
#pragma warning restore S2696 // Instance members should not write to "static" fields
203+
#pragma warning restore S3971 // "GC.SuppressFinalize" should not be called
219204
}
Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,79 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Runtime.CompilerServices;
5+
46
namespace System
57
{
6-
using Runtime.CompilerServices;
7-
88
/// <summary>
99
/// Controls the system garbage collector, a service that automatically reclaims unused memory.
1010
/// </summary>
1111
public static class GC
1212
{
13+
#pragma warning disable S4200 // Native methods should be wrapped
14+
15+
/// <summary>
16+
/// Enables or disables the output of garbage collection messages.
17+
/// </summary>
18+
/// <param name="enable"><see langword="true"/> to enable the output of GC messages; otherwise, <see langword="false"/>.</param>
19+
/// <remarks>
20+
/// <para>
21+
/// Enabling GC messages may not always result in output, depending on the target build options.
22+
/// For example, RTM builds, which remove all non-essential features, may not output these messages.
23+
/// </para>
24+
/// <para>
25+
/// This method is specific of .NET nanoFramework implementation. There is no equivalent in full .NET API.
26+
/// </para>
27+
/// </remarks>
1328
[MethodImpl(MethodImplOptions.InternalCall)]
14-
private static extern bool AnyPendingFinalizers();
29+
public static extern void EnableGCMessages(bool enable);
1530

1631
/// <summary>
17-
/// Suspends the current thread until the thread that is processing the queue of finalizers has emptied that queue.
32+
/// Retrieves the heap size excluding fragmentation. For example if the total GC heap size is 1MB and fragmentation, ie, space taken up by free objects, takes up 400kB, this API would report 600kB. A parameter indicates whether this method can wait a short interval before returning, to allow the system to collect garbage and finalize objects.
1833
/// </summary>
19-
public static void WaitForPendingFinalizers()
20-
{
21-
while (AnyPendingFinalizers()) Threading.Thread.Sleep(10);
22-
}
34+
/// <param name="forceFullCollection"><see langword="true"/> to indicate that this method can wait for garbage collection and heap compaction to occur before returning; otherwise, <see langword="false"/>.</param>
35+
/// <returns>The heap size, in bytes, excluding fragmentation.</returns>
36+
public static long GetTotalMemory(bool forceFullCollection) => Run(forceFullCollection);
2337

2438
/// <summary>
25-
/// Requests that the system not call the finalizer for the specified object.
39+
/// Requests that the system call the finalizer for the specified object for which <see cref="SuppressFinalize"/> has previously been called.
2640
/// </summary>
27-
/// <param name="obj">The object that a finalizer must not be called for. </param>
41+
/// <param name="obj">The object that a finalizer must be called for.</param>
42+
/// <exception cref="ArgumentNullException"><paramref name="obj"/> is <see langword="null"/>.</exception>
2843
[MethodImpl(MethodImplOptions.InternalCall)]
29-
public static extern void SuppressFinalize(Object obj);
44+
public static extern void ReRegisterForFinalize(object obj);
45+
46+
/// <summary>
47+
/// Forces an immediate garbage collection of all generations.
48+
/// </summary>
49+
/// <remarks>
50+
/// Use this method to try to reclaim all memory that is inaccessible. It performs a blocking garbage collection of all generations.
51+
/// All objects, regardless of how long they have been in memory, are considered for collection; however, objects that are referenced in managed code are not collected. Use this method to force the system to try to reclaim the maximum amount of available memory.
52+
/// </remarks>
53+
public static void Collect() => Run(true);
3054

3155
/// <summary>
32-
/// Requests that the system call the finalizer for the specified object for which SuppressFinalize has previously been called.
56+
/// Requests that the common language runtime not call the finalizer for the specified object.
3357
/// </summary>
34-
/// <param name="obj">The object that a finalizer must be called for. </param>
58+
/// <param name="obj">The object whose finalizer must not be executed.</param>
59+
/// <exception cref="ArgumentNullException"><paramref name="obj"/> is <see langword="null"/>.</exception>
3560
[MethodImpl(MethodImplOptions.InternalCall)]
36-
public static extern void ReRegisterForFinalize(Object obj);
61+
public static extern void SuppressFinalize(object obj);
3762

63+
/// <summary>
64+
/// Suspends the current thread until the thread that is processing the queue of finalizers has emptied that queue.
65+
/// </summary>
66+
public static void WaitForPendingFinalizers()
67+
{
68+
while (AnyPendingFinalizers()) Threading.Thread.Sleep(10);
69+
}
70+
71+
#pragma warning restore S4200 // Native methods should be wrapped
72+
73+
[MethodImpl(MethodImplOptions.InternalCall)]
74+
private static extern bool AnyPendingFinalizers();
75+
76+
[MethodImpl(MethodImplOptions.InternalCall)]
77+
private static extern uint Run(bool compactHeap);
3878
}
3979
}

0 commit comments

Comments
 (0)