Skip to content

Commit e6036b2

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 e6036b2

File tree

3 files changed

+111
-69
lines changed

3 files changed

+111
-69
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: 25 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,20 @@ public void SystemGC1_Test()
5454
/// 6. Verify that object has been collected
5555
/// </summary>
5656
///
57-
// Tests ReRegisterForFinalize
58-
// Create a FinalizeObject.
57+
58+
OutputHelper.WriteLine("Tests ReRegisterForFinalize");
59+
OutputHelper.WriteLine("Create a FinalizeObject.");
60+
5961
FinalizeObject mfo = new FinalizeObject();
6062
m_hasFinalized1 = false;
6163
m_hasFinalized2 = false;
6264

6365
// Release reference
66+
OutputHelper.WriteLine("Release reference");
6467
mfo = null;
6568

66-
// Allow GC
67-
GC.WaitForPendingFinalizers();
69+
OutputHelper.WriteLine("Allow GC");
70+
GC.Collect();
6871

6972
int sleepTime = 1000;
7073
int slept = 0;
@@ -85,10 +88,10 @@ public void SystemGC1_Test()
8588
// FinalizeObject.m_currentInstance field. Setting this value
8689
// to null and forcing another garbage collection will now
8790
// cause the object to Finalize permanently.
88-
// Reregister and allow for GC
89-
FinalizeObject.m_currentInstance = null;
9091

91-
GC.WaitForPendingFinalizers();
92+
OutputHelper.WriteLine("Reregister and allow for GC");
93+
FinalizeObject.m_currentInstance = null;
94+
GC.Collect();
9295

9396
sleepTime = 1000;
9497
slept = 0;
@@ -119,18 +122,19 @@ public void SystemGC2_Test()
119122
/// 6. Verify that object has not been collected
120123
/// </summary>
121124
///
122-
// Tests SuppressFinalize
123-
// Create a FinalizeObject.
125+
126+
OutputHelper.WriteLine("Tests SuppressFinalize");
127+
OutputHelper.WriteLine("Create a FinalizeObject");
124128
FinalizeObject mfo = new FinalizeObject();
125129
m_hasFinalized1 = false;
126130
m_hasFinalized2 = false;
127131

128-
// Releasing
132+
OutputHelper.WriteLine("Releasing");
129133
GC.SuppressFinalize(mfo);
130134
mfo = null;
131135

132-
// Allow GC
133-
GC.WaitForPendingFinalizers();
136+
OutputHelper.WriteLine("Allow GC");
137+
GC.Collect();
134138

135139
int sleepTime = 1000;
136140
int slept = 0;
@@ -161,56 +165,28 @@ public void SystemGC3_Test()
161165
/// </summary>
162166
///
163167

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

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

171-
// Create a FinalizeObject.
173+
OutputHelper.WriteLine("Create a FinalizeObject");
172174
FinalizeObject mfo = new FinalizeObject();
173175
m_hasFinalized1 = false;
174176
m_hasFinalized2 = false;
175177

176-
// Releasing
178+
OutputHelper.WriteLine("Releasing");
177179
mfo = null;
178180

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
181+
OutputHelper.WriteLine("Wait for GC");
182+
GC.Collect();
194183
GC.WaitForPendingFinalizers();
195184

196-
// Releasing again
185+
OutputHelper.WriteLine("Releasing again");
197186
FinalizeObject.m_currentInstance = null;
198187

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
188+
OutputHelper.WriteLine("Wait for GC");
189+
GC.Collect();
214190
GC.WaitForPendingFinalizers();
215191

216192
Assert.IsTrue(m_hasFinalized2);
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)